Actually, that would be quite feasible in the scope of the current proposal.
This example already works:
package newtype
import scala.annotation.targetName
trait Monoid[A] {
extension (x: A) @targetName("mappend") def <> (y: A): A
def mempty: A
}
extension [T](xs: List[T])(using m: Monoid[T])
def foldM: T = xs.fold(m.mempty)(_ <> _)
object newtypes {
opaque type Sum[A] = A
object Sum {
def apply[T](x: T): Sum[T] = x
def unapply[T](w: Sum[T]): Some[T] = Some(w)
}
opaque type Prod[A] = A
object Prod {
def apply[T](x: T): Prod[T] = x
def unapply[T](w: Prod[T]): Some[T] = Some(w)
}
opaque type Logarithm = Double
object Logarithm {
def apply(d: Double): Logarithm = math.log(d)
def unapply(l: Logarithm): Some[Double] = Some(math.exp(l))
}
given Monoid[Prod[Double]] {
extension (x: Prod[Double]) @targetName("mappend") def <> (y: Prod[Double]): Prod[Double] = x * y
def mempty: Prod[Double] = 1
}
given Monoid[Sum[Double]] {
extension (x: Sum[Double]) @targetName("mappend") def <> (y: Sum[Double]): Sum[Double] = x + y
def mempty: Sum[Double] = 0
}
given (using m: Monoid[Sum[Double]]) as Monoid[Prod[Logarithm]] = m
}
Now running this:
import newtype._
import newtypes._
object Main {
def main(args: Array[String]): Unit = {
val dProd: Prod[Double] = List(1.0,2.0,3.0,4.0).map(x => Prod(x)).foldM
val lProd: Prod[Logarithm] = List(1.0,2.0,3.0,4.0).map(x => Prod(Logarithm(x))).foldM
println(s"Regular Product: ${dProd}")
println(s"Logarithm Product: log(${lProd})")
lProd match{ case Prod(Logarithm(d)) => println(s"Logarithm Product: ${d}")}
}
}
Prints this:
Regular Product: 24.0
Logarithm Product: log(3.1780538303479453)
Logarithm Product: 23.999999999999993
Currently, you can only do this when you have all opaques in scope, but your example:
Could be implemented using something like this:
opaque type Name = String
given (using m: Show[String]) as Show[Name] = m
given (using m: Codec[String]) as Codec[Name] = m
given (using m: Monoid[String]) as Monoid[Name] = m
Maybe this sugar wouldn’t be such a bad fit:
opaque type Name = String derives Show, Codec, Monoid
This would essentially be equivalent to Haskell’s GeneralisedNewtypeDeriving
.
If you want to generalize this further into something like DerivingVia
, then maybe this would not be a bad idea:
opaque type Name = String derives Show using Show[String], Codec using Codec[String], Monoid using Monoid[String]
But I think the first would be much easier to get people on board with.