I have tried your workaround in Can We Wean Scala Off Implicit Conversions?
This still uses the concept of
Conversion
, but it’s no longer an implicit conversion . The conversion is applied explicitly whereever it is needed. The idea is that with the help of using clauses we can “push” the applications of conversions from user code to a few critical points in the libraries
, and it works - at the expense of some kind of algebraic purity in the type class definition:
import scala.language.implicitConversions
given id[T] as Conversion[T, T] = identity
trait SemiGroup[T]:
extension [U](x: U)(using c: Conversion[U, T]) def combine (y: T): T
trait Monoid[T] extends SemiGroup[T]:
def unit: T
class A
object A:
given Conversion[Int, A]:
def apply(n: Int) = ???
given Monoid[A]:
extension [U](x: U)(using c: Conversion[U, A]) def combine (y: A) = ???
def unit = ???
val a = new A
a.combine(a) // ok
a.combine(1) // ok
1.combine(a) // ok
There is still an issue if we introduce a second type B:
class B
object B:
given Conversion[Int, B]:
def apply(n: Int) = ???
given Monoid[B]:
extension [U](x: U)(using c: Conversion[U, B]) def combine (y: B) = ???
def unit = ???
val b = new B
b.combine(b) // ok
b.combine(1) // ok
1.combine(b)
^
Found: (1 : Int)
Required: ?{ combine: ? }
Note that implicit extension methods cannot be applied because they are ambiguous;
both object given_Monoid_A and object given_Monoid_B provide an extension method `combine` on (1 : Int)
If someone has an idea how to solve this…