conversions can be seen as special cases of typeclasses
No, unfortunately this is not the case. For example, inline conversions are NOT typeclasses. They cannot be used as values of a typeclass because they are NOT valid at runtime! Currently dotty makes you jump through some hoops to define a macro conversion – you must first define a subclass of Conversion that will error at runtime, an error situation that’s forced by the new encoding – and only then you can define an inline conversion:
trait MyInlineConversion[-A, +B] extends Conversion[A, B] {
def apply(a: A): B = throw new Error("""Tried to call a macro conversion
at runtime! This Conversion value is invalid at runtime, it must be applied
and inlined by the compiler only, you shouldn't summon it, sorry""")
}
trait DslExpr[+A]
given MyInlineConversion[A, DslExpr[A]] {
inline def apply(expr: A): DslExpr[A] = {
// analyze expr and produce new DslExpr...
...
}
}
More so, the Conversion
typeclass rules out two more types of implicit conversions:
-
Path-dependent conversions such as
implicit def x(a: A): a.Out
are inexpressible withConversion
, there’s no syntax to expressConversion[(a: A), a.Out]
-
Path-dependent conversions that summon more implicit parameters that depend on the input value, such as
implicit def x(a: A)(implicit t: Get[a.T]): t.Out
are inexpressible withConversion
, there’s no way to append more implicit argument lists to thedef apply(a: A): B
method ofConversion
!
All of the above types of conversions are heavily used in mainstream Scala libraries, such as Akka, Sbt & Shapeless. I point the specific usages in the dotty issue
I do not believe that implicit conversions deserve to be gimped by completely ruling out at least two of their currently used forms and by making the third form - macro conversions - inconvenient to define and unsafe to use (a library user always risks to summon a dud Conversion
object that will error at runtime if there are macro conversions in scope). As such I think we need to address the following issues:
- Create a syntax specifically for conversions, that will bring back path-dependency and would better express intent than the
given Conversion
definitions:
conversion on (a: A) with (t: Get[a.T]): t.Out = ...
- Create a new type
InlineConversion[A, B]
- a super type of Conversion, that would be summonable ONLY ininline def
s. That way, abstractions on top of summonable macros can be easily built, but at the same time theConversion
type will be unpolluted by inline conversions that are invalid at runtime.