Currently learning about Scala 3 implicits but I'm having a hard time grasping what the as and with keywords do in a definition like this:
given listOrdering[A](using ord: Ordering[A]) as Ordering[List[A]] with
def compare(a: List[A], b: List[A]) = ...
I tried googeling around but didn't really find any good explanation. I've checked the Scala 3 reference guide, but the only thing I've found for as is that it is a "soft modifier" but that doesn't really help me understand what it does... I'm guessing that as in the code above is somehow used for clarifying that listOrdering[A] is an Ordering[List[A]] (like there's some kind of typing or type casting going on?), but it would be great to find the true meaning behind it.
As for with, I've only used it in Scala 2 to inherit multiple traits (class A extends B with C with D) but in the code above, it seems to be used in a different way...
Any explanation or pointing me in the right direction where to look documentation-wise is much appreciated!
Also, how would the code above look if written in Scala 2? Maybe that would help me figure out what's going on...
The
as-keyword seems to be some artifact from earlier Dotty versions; It's not used in Scala 3. The currently valid syntax would be:The Scala Book gives the following rationale for the usage of
withkeyword ingiven-declarations:i.e.
becomes
The choice of the
withkeyword seems of no particular importance: it's simply some keyword that was already reserved, and that sounded more or less natural in this context. I guess that it's supposed to sound somewhat similar to the natural language phrases likeThis usage of
withis specific togiven-declarations, and does not generalize to any other contexts. The language reference shows that thewith-keyword appears as a terminal symbol on the right hand side of theStructuralInstanceproduction rule, i.e. this syntactic construct cannot be broken down into smaller constituent pieces that would still have thewithkeyword.I believe that understanding the forces that shape the syntax is much more important than the actual syntax itself, so I'll instead describe how it arises from ordinary method definitions.
Step 0: Assume that we need instances of some typeclass
FooLet's start with the assumption that we have recognized some common pattern, and named it
Foo. Something like this:Step 1: Create instances of
Foowhere we need them.Now, assuming that we have some method
fthat requires aFoo[Int]...... we could write down an instance of
Fooevery time we need it:FooFooexactly where we need themStep 2: Methods
Writing down the methods
fooandbaron every invocation offwill very quickly become very boring and repetitive, so let's at least extract it into a method:Now we don't have to redefine
fooandbarevery time we need to invokef; Instead, we can simply invokelistFoo:FoorepeatedlyStep 3:
usingIn situations where there is basically just one canonical
Foo[A]for everyA, passing arguments such aslistFoo[Int]explicitly quickly becomes tiresome too, so instead, we declarelistFooto be agiven, and make thefoo-parameter offimplicit by addingusing:Now we don't have to invoke
listFooevery time we callf, because instances ofFooare generated automatically:Step 4: Deduplicate type declarations
The
given listFoo[A]: Foo[List[A]] = new Foo[List[A]] {looks kinda silly, because we have to specify theFoo[List[A]]-part twice. Instead, we can usewith:Now, there is at least no duplication in the type.
given xyz: SomeTrait = new SomeTrait { }is noisy, and contains duplicated partswith-syntax, avoid duplicationStep 5: irrelevant names
Since
listFoois invoked by the compiler automatically, we don't really need the name, because we never use it anyway. The compiler can generate some synthetic name itself:givens where they aren't needed.All together
In the end of the process, our example is transformed into something like
foo/barmethods forLists.givens explicitly, the compiler does this for us.givendefinition