Pre-SIP derives clauses for opaque types
I propose two changes to the Scala language:
-
Adding a derives
clause for opaque
types that will be desugared to a normal call to the derived
method on the typeclass.
e.g.
opaque type USD = Int derives Show
will be desugared to
opaque type USD = Int
object USD {
given Show[USD] = Show.derived[USD]
}
I think that this use case might not be very popular, especially since there are no Mirror
s for opaque
types. But they still can be generated using macros, so it’s the only reasonable thing to keep it consistent with classes.
-
Adding a derives via
clause for opaque
types that will be desugared to a proxy to another types instance.
e.g.
opaque type USD = Int derives Show via Int
will be desugared to
opaque type USD = Int
object USD {
given (using intShow: Show[Int]): Show[USD] = intShow.asInstanceOf[Show[USD]] // obviously try to avoid the `asInstanceOf` if possible
}
I think that this might be by far the most useful case, since it is often that case that the instance for the underlying type can be used for the opaque
type.
When it comes to syntax, the via
clause will have to be used per typeclass, so the following two declarations will NOT be equivalent.
opaque type USD = Int
derives Eq, Show via Int
// and
opaque type USD = Int
derives Eq via Int, Show via Int
Some variations
The downside of this syntax is that it introduces a new keyword. And although via
shouldn’t be a popular symbol name, this change won’t nearly be common enough to justify introducing a new keyword.
All syntax option to consider:
derives Show via Int
derives Show using Int
derives Show as Int
Another notion to consider is the desugaring of derives via
clauses. Two possibilities:
-
given (using intShow: Show[Int]): Show[USD] = intShow.asInstanceOf[Show[USD]]
-
given Show[USD] = Show.derived[Int].asInstanceOf[Show[USD]]
Any feedback greatly appreciated.
10 Likes
Personally I have some doubts regarding the position of derives
clauses. To me it would feel more consistent to put them on the left side of the definition, e.g.
opaque type USD derives Show = Int
as this influences the type alias itself rather than it’s implementation, similarly to how we place type bounds, e.g
opaque type USD <: Currency = Int
On the other hand with might look weird when we derive a lot of type classes, e.g.
opaque type USD
derives Foo1
derives Foo2
derives Foo3 = Int
There was also a proposal allowing to pass extra parameters to derives
clauses explicitly Ergonomics of configurable derivation. I’m wondering if these two features could work well together.
4 Likes
I am not sure about the advantage gained by the second construct, the derives via
. If I understand correctly, in the given snippets
opaque type USD = Int derives Show via Int
Is effectively equivalent to:
opaque type USD = Int derives Show
As it is in the scope where the opaque type is defined. What would be the use case for it?
Edit: After explanation from the author I see that there is important difference as the derives via
does not invoke derived
, but instead “forwards” the given. Nevertheless, given how derives
syntax works, I think that a lot of people would assume that
opaque type USD = Int derives Show via Int
Would mean:
opaque type USD = Int
object USD {
given Show[USD] = Show.derived[Int]
}
2 Likes
I couldn’t explain it, but for me
opaque type USD derives Show = Int
looks weirder than
opaque type USD = Int derives Show
I think it also has the advantage of going well with bounds:
opaque type USD <: Number = Int derives Show
vs
opaque type USD derives Show <: Number = Int
or
opaque type USD <: Number derives Show = Int
I agree that the via
mechanism is a bit unexpected, but if it behaves intuitively enough for users not having to worry about it, it is not such a concern
4 Likes
I would absolutely love to have derives
on opaques.
Not having it makes me not want to use opaque types so much as I would, especially combined with the fact that summoning given Show[USD] = Show[Int]
in scope of an opaque will give you an infinite recursion.
From the POV of someone authoring these opaque types, I would say derives Show
should just forward by default. I can see how it’s inconsistent with the default behavior of derives
, but if it’s the thing that most people are going to do, maybe it’s better than the more verbose derives Show via Int
? Besides, even the latter may be confusing, as @szymon-rd has pointed out.
this for me is “the way” - the public API is all on the left of =
and the bounds come before derives, like extends comes before derives for classes
8 Likes