Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
bernardopinto
@bernardopinto

Hi tere,
I'm exploring how cats does type conversion, in this specific case, Either => EitherOps conversion.
I've tracked up to EitherSyntax that implements an implicit def catsSyntaxEither converting an Either => EitherOps.
However I don't get where/how the conversion is summoned. I would expect some implicit class to allow infix notation to do
something like Right().bimap(leftChannel, rightChannel).
either syntax

Would appreciate any help :)

Rob Norris
@tpolecat
The conversion is typically in scope via the import of cats.syntax.all._
@ cats.syntax.all.catsSyntaxEither(Right()) 
res3: cats.syntax.EitherOps[Nothing, Unit] = cats.syntax.EitherOps@f29f2af0
Maybe I don't understand your question.
Luis Miguel Mejía Suárez
@BalmungSan
@bernardopinto do you know that an implicit class is just sugar syntax for a class and an implicit def (implicit conversion)?
That is why they can not be top-level and why libraries like cats don't use them but rather just have normal class and the conversions in some traits that are shared.
Does that answer your question? Or did I misunderstood it?
Adam Rosien
@arosien

What I am doing (provisionally) is to create a case class FsString private (chars: Seq[Char]) which can only be built by cats.parse or by a constructor in the object that does the verification of the content first. That should give me enough type safety for the moment. (Good to know about Literally).

@bblfish:matrix.org you may know already, but refined automates that stuff. there may be other "newtype"-like support that removes the tagged type approach of refined

bblfish
@bblfish:matrix.org
[m]
bernardopinto
@bernardopinto
@BalmungSan I actually didn't know that.
But in cats.syntax.either the final class EitherOps does not extends EitherSyntax. And in EitherSyntax is where catsSyntaxEither (Either => EitherOps) lives. So I'm not sure how that connection happens.
Also @tpolecat how does cats gets from a Right(value): Either[E, A] => Bifunctor[Either] just by importing import cats.syntax.either._ and calling Right(value).bimap(ec, vc)?
I have an example here: https://github.com/bernardopinto/ammonite-scripts/blob/master/BifunctorStudy.sc
Luis Miguel Mejía Suárez
@BalmungSan

@bernardopinto
Ok, let's try to go step by step in more or less the way the compiler does.

Given:

import cats.syntax.all._
val either: Either[E, A] = Right(value)
either.bimap(ec, vc)

The compiler sees a call to bimap on a value of type Either[E, A], the type Either[E, A] doesn't have such a method so the compiler tries to search for implicit conversion from Either[E, A] into anything that has a bimap method with the correct signature; remember it has a limited scope from where it searches.

In this case, it will eventually search in the lexical scope, i.e., in whatever is imported.
The line import cats.syntax.all._ imports an implicit conversion from Either into EitherOps which does has such bimap method.
But, how does that import provided that extension?

You can see that conversion defined here inside the EitherSyntax trait.
Then here we can see that the AllSyntax trait extends the EitherSyntax one.
And finally here you can see that the all object extends the AllSyntax trait; thus having inside it the implicit conversion.
Which is ultimately put in scope with the import

bernardopinto
@bernardopinto
@BalmungSan alright got it thanks :)
I was not familiar with regular use of type conversions like this. But it makes sense now, goes back to what you said about having an implicit def and a case class.
Luis Miguel Mejía Suárez
@BalmungSan
@bernardopinto note is not a case class, is a value class. You do not want the class that contains the extension methods to really exists.
bernardopinto
@bernardopinto
@BalmungSan true, mental typo. Thanks for the help :)
Luis Miguel Mejía Suárez
@BalmungSan
You are welcome :)
Adam Rosien
@arosien
i usually think of it like:
case class Foo(s: String)

implicit class FooOps(foo: Foo) extends AnyVal {
  def hello(): String = s"Hello $foo.s!"
}
val foo = Foo("Luis")

- foo.hello() // the compiler replaces this code with the following
+ new FooOps(foo).hello() // since FooOps is a value class, no FooOps actually gets instantiated
Adam Rosien
@arosien
but, as always, @BalmungSan :thumbsup:
Luis Miguel Mejía Suárez
@BalmungSan
:grimacing:
Adam Rosien
@arosien
scala 3 will lower the conceptual overhead of this stuff
Eugene Apollonsky
@chessman

hi! rookie's question. I have a List[ValidatedNec[Error, Any]] and I want to collect left parts as Option[NonEmptyChain[Error]]. I came up with

    List(Error("a").invalidNec[String], Error("b").invalidNec[Any]).map(_.fold(Some(_), _ => None)).combineAll

but it looks ugly. is there a better way to do it?

daenyth
@daenyth:matrix.org
[m]
mmm
Luis Miguel Mejía Suárez
@BalmungSan
list.sequence.swap.toOption ?
You may even avoid the List[Validated] in the first place if you can use parTraverse but you would need to show more code for that.
Adam Rosien
@arosien
separate
def separate[G[_, _], A, B](fgab: F[G[A, B]])(implicit FM: Monad[F], G: Bifoldable[G]): (F[A], F[B])
Separate the inner foldable values into the "lefts" and "rights"
Example:
scala> import cats.implicits._
scala> val l: List[Either[String, Int]] = List(Right(1), Left("error"))
scala> Alternative[List].separate(l)
res0: (List[String], List[Int]) = (List(error),List(1))
Eugene Apollonsky
@chessman
thanks, looks good
bblfish
@bblfish:matrix.org
[m]
I see there is initial work on fthomas/refined#921.
Adam Rosien
@arosien
noel and i presented about various options for refinements, etc., for a larger view on the subject: https://www.youtube.com/watch?v=w7FuQiSi48w
but yes, refined is quite amazing
bblfish
@bblfish:matrix.org
[m]
Oh thanks.
I am actually starting to wonder if refinement is not actually extremely useful when working with data. So RFC8941 defines a bunch of types for HTTP headers. But each header will only use a subset of some of those types. So eg. a Signing HTTP Messages header will have a SfDict, but restricted to certain types of lists, and certain types of attributes. So it would be interesting if one could keep the underlying data structure but after testing it that it matches some criteria, refine it without changing the structure. Then one could use the underlying data structures without always needing to copy them to new structures.
Adam Rosien
@arosien
yes!
bblfish
@bblfish:matrix.org
[m]
now I understand what sealed abstract case classes are about :-)
2 replies
Rob Norris
@tpolecat
It's a good trick.
I wish it weren't a trick but I'm glad it's possible.
bblfish
@bblfish:matrix.org
[m]
It would be nice to have an update on arosien (Adam Rosien)'s talk on how to use opaque type classes for refinement now that they are widely available with scala3
Also in that talk Adam mentions how AnyVal's are not that efficient. I wonder if the same criticism is true of extension methods.
bblfish
@bblfish:matrix.org
[m]
I guess one would create opaque types using object { def apply(...) } which would just return the same object, and then use extension methods, that limit how the type can be used, to only the valid things that can be done with the validated subset.
Luis Miguel Mejía Suárez
@BalmungSan

I wonder if the same criticism is true of extension methods.

No, one-level extension methods is one of the few cases where AnyVal always work as expected.

Adam Rosien
@arosien
AnyVal are efficient, but there are a set of cases where they don't work. they are generally good though.
bblfish
@bblfish:matrix.org
[m]
did you do a follow up talk on opaque types?
Adam Rosien
@arosien
i know nothing about opaque types, in the scala 3 sense
it probably works most of the time, and doesn't work in weird ways
bblfish
@bblfish:matrix.org
[m]
There is a nice intro talk to it here: https://www.youtube.com/watch?v=8-b2AoctkiY I am just watching now.
Luis Miguel Mejía Suárez
@BalmungSan
AFAIK they always work, because they are exactly that just opaque type alias.
The downside is that they are very raw.
You need to re-create a lot of functionality.
Christopher Davenport
@ChristopherDavenport
They work, but the scopes and rules are pretty difficult to get rightl
Atleast if you want to combine static validation and opaque types, which I often do
Luis Miguel Mejía Suárez
@BalmungSan
Why do you need the inner scope?