Synthesize constructor for opaque types

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.

1 Like