Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to define a scala.ValueOf for tuples in scala 3?

The ValueOf type function can be used in the contextual parameters list of a method to pick the single inhabitant of singleton type, or reject the implicit resolution if the type argument isn't a singleton.

enum Color { case red, green, blue }
def singleInhabitantOf[T](using  holder: ValueOf[T]): T = holder.value
println(singleInhabitantOf[Color.red.type] == Color.red) // outputs true

But it is limited. It doesn't work with tuples whose component types are all singletons.

singleInhabitantOf[(Color.red.type, Color.blue.type)] // compile error

The error message is: No singleton value available for (Color.red, Color.blue); eligible singleton types for ValueOf synthesis include literals and stable paths.

So I tried to create a version for tuples this way:

import scala.Tuple

type ValuesOf[T <: Tuple] <: Tuple = T match {
    case EmptyTuple => EmptyTuple
    case h *: t => ValueOf[h] *: ValuesOf[t]
}

def singleInhabitantsOf[T<:Tuple](using holder: ValuesOf[T]): Tuple = holder // a tuple mapping is missing here but how to implement it is another question.

singleInhabitantsOf[(Color.red.type, Color.blue.type)] // Copile error

Unfortunately, the compiler complains saying: No given instance of type ValuesOf[(Color.red, Color.blue)] was found for parameter holder. Apparently it looks for a tuple instance in the implicit context instead of synthetizing a tuple with the singletons instances.

However

summonAll[ValuesOf[(Color.red.type, Color.blue.type)]]

compiles and runs fine. So I could rewrite the method using summonAll like this

inline def singleInhabitantsOf2[T <: Tuple]: Tuple = summonAll[ValuesOf[T]] 

But this solution is not useful for my final purpose, for which the check of singletonness should be in the method signature, such that the compiler don't start processing the body if something is wrong.

Edit @DmytroMitin showed me that the previous paragraph is incorrect. It is okay and sometimes unavoidable to involve the body of the given clause in determining when it provides an instance or not.

Any idea on how to define a ValueOf that works with tuples in the context parameters list?

like image 806
Readren Avatar asked Oct 16 '25 00:10

Readren


2 Answers

Type classes and match types are two ways to perform type-level calculations in Scala 3 (like type projections and type classes in Scala 2, type families and type classes in Haskell).

Mixing type classes and match types can be tricky:

How to prove that `Tuple.Map[H *: T, F] =:= (F[H] *: Tuple.Map[T, F])` in Scala 3

scala 3 map tuple to futures of tuple types and back

Scala3 type matching with multiple types


It seems you want type classes rather than match types

trait ValuesOf[T <: Tuple]:
  def value: T

object ValuesOf:
  given ValuesOf[EmptyTuple] with
    val value = EmptyTuple

  given [h, t <: Tuple](using vh: ValueOf[h], vt: ValuesOf[t]): ValuesOf[h *: t] with
    val value = vh.value *: vt.value

def singleInhabitantsOf[T <: Tuple](using holder: ValuesOf[T]): T = holder.value

singleInhabitantsOf[(Color.red.type, Color.blue.type)] // (red,blue)

With match types you can do

type SingleInhabitantsOf[T <: Tuple] <: Tuple = T match
  case EmptyTuple => EmptyTuple
  case h *: t => h *: SingleInhabitantsOf[t]

inline def singleInhabitantsOf0[T <: Tuple]: SingleInhabitantsOf[T] =
  inline erasedValue[T] match
    case _: EmptyTuple => EmptyTuple
    case _: (h *: t) => valueOf[h] *: singleInhabitantsOf0[t]

// to specify return type, not necessary
inline def singleInhabitantsOf[T <: Tuple]: T = summonFrom {
  case given (SingleInhabitantsOf[T] =:= T) => singleInhabitantsOf0[T]
}

singleInhabitantsOf[(Color.red.type, Color.blue.type)] // (red,blue)

Using standard operations on tuples you can do

type ValuesOf[T <: Tuple] = Tuple.Map[T, ValueOf]

type InvValueOf[V] = V match
  case ValueOf[a] => a

type SingleInhabitantsOf[T <: Tuple] = Tuple.Map[ValuesOf[T], InvValueOf]

// can't express return type
inline def singleInhabitantsOf0[T <: Tuple] /*: Tuple.Map[? <: ValuesOf[T], InvValueOf]*//*: SingleInhabitantsOf[T]*/ =
  val valuesOf = summonAll[ValuesOf[T]]
  valuesOf.map[InvValueOf]([b] => (y: b) => y match
    case v: ValueOf[a] => v.value
  ): Tuple.Map[valuesOf.type, InvValueOf]

// to specify return type, not necessary
inline def singleInhabitantsOf[T <: Tuple]: T = summonFrom {
  case _: (SingleInhabitantsOf[T] =:= T) => singleInhabitantsOf0[T]
}

singleInhabitantsOf[(Color.red.type, Color.blue.type)] // (red,blue)

But this solution is not useful for my final purpose, for which the check of singletonness should be in the method signature, such that the compiler don't start processing the body if something is wrong.

Sounds like a little weird requirement. You can always "hide" implicit parameters from the left at the right. In Scala 2 you can rewrite

def foo[A](implicit tc: TC[A]): Unit = ()

as

def foo[A]: Unit = macro fooImpl[A]

def fooImpl[A: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
  import c.universe._
  c.inferImplicitValue(weakTypeOf[TC[A]])
  q"()"
}

In Scala 3 you can rewrite

def foo[A](using TC[A]): Unit = ()

as

inline def foo[A]: Unit =
  summonInline[TC[A]]
  ()

Maybe you could tell more about your actual problem (maybe starting a new question) and we could look whether it can be solved via match types, summonAll, constValueTuple and other stuff from scala.compiletime.*.


But I think it isn't a good practice to relay on compilation errors as decider. That is error message unfriendly and hard to understand.

I do not mean to argue on code styles but error messages depend on a developer

def foo[A](using TC[A]): Unit = ()

foo[Int] // No given instance of type App.TC[Int] was found for parameter x$1 of method foo in object App
inline def foo[A]: Unit =
  summonInline[TC[A]]
  ()

foo[Int] // No given instance of type App.TC[Int] was found
def foo[A](using @implicitNotFound("No TC!!!") tc: TC[A]): Unit = ()

foo[Int] // No TC!!!
inline def foo[A]: Unit =
  summonFrom {
    case _: TC[A] => ()
    case _ => error("No TC!!!")
  }

foo[Int] // No TC!!!

I do not insist that you should always use implicits "on the right" rather than implicitls "on the left". I just wanted to show you that you have a choice. Surely, implicits "on the left" are more standard. But implicits "on the right" can sometimes be more flexible. For example please see my answer in (already mentioned) scala 3 map tuple to futures of tuple types and back . I'm using there implicits "on the right" nested deeply in the method body. This could be harder to achieve with implicits "on the left".

like image 154
Dmytro Mitin Avatar answered Oct 17 '25 22:10

Dmytro Mitin


Another approach is just to use a traditional head-tail derivation of the typeclass.
I am almost sure you can reuse the existing ValueOf one, but personally I prefer to write my own one just to avoid any issues:

trait GetSingletonValue[A]:
  def value: A
  
object GetSingletonValue:
  /** Construct a GetSingletonValue for any value for which there is a ValueOf instance. */  
  given [A](using ev: ValueOf[A]): GetSingletonValue[A] with
    override final val value: A = ev.value

  /** Base case for the Tuple recursion. */
  given GetSingletonValue[EmptyTuple] with
    override final val value: EmptyTuple = EmptyTuple

  /** Recursive case for the Tuple recursion. */
  given [A, T <: Tuple](using head: GetSingletonValue[A], tail: GetSingletonValue[T]): GetSingletonValue[A *: T] with
    override final val value: A *: T = head.value *: tail.value
end GetSingletonValue

You can then request an instance for any singleton:

summon[GetSingletonValue[Color.Green.type]].value // Green

And of any Tuple of singletons:

summon[GetSingletonValue[(Color.Red.type, Color.Blue.type)]].value // (Red, Blue)

You can then add a couple of useful helper methods to reduce the boilerplate:

inline def apply[A](using ev: GetSingletonValue[A]): ev.type = ev

inline def ofValue[A](using ev: GetSingletonValue[A]): A = ev.value

And use them like this:

GetSingletonValue.ofValue[Color.Green.type] // Green
GetSingletonValue.ofValue[(Color.Red.type, Color.Blue.type) // (Red, Blue)

Now, you want to just be able to get all the singletons of a given enum.
For that, we would need to materialize a tuple type that represents all the types of the enum. Thankfully Scala 3 already give us that in the form of Mirror.SumOf[T]#MirroredElemTypes.

val colorMirror = summon[scala.deriving.Mirror.SumOf[Color]]
GetSingletonValue.ofValue[colorMirror.MirroredElemTypes] // (Red, Green, Blue)

However, having to materialize the Mirror ourselves in order to request the values is redundant boilerplate.
Thus, we could just provide a nice helper:

inline def ofEnum[E](using m: scala.deriving.Mirror.SumOf[E], ev: GetSingletonValue[m.MirroredElemTypes]): m.MirroredElemTypes = ev.value

You can also code it like this if you prefer:

inline def ofEnum2[E](using m: scala.deriving.Mirror.SumOf[E]): m.MirroredElemTypes =
    scala.compiletime.summonInline[GetSingletonValue[m.MirroredElemTypes]].value

They both are essentially the same thing. Personally, I prefer the first one, it is more usual for Scala 2 folks and needs less macro wizardry.

Both work as expected:

GetSingletonValue.ofEnum[Color] // (Red, Green, Blue)
GetSingletonValue.ofEnum2[Color] // (Red, Green, Blue)

You can see the code running in Scastie.


A final note, AFAIK, in Scala 3 the head-tail approach is not recommended any more due to "high-level" constructs for derivation. But, personally, I always struggle to use them for a lot of reasons. Shapeless 3 is a better approach to the high-level idea, but I still prefer simple recursion.

like image 36
Luis Miguel Mejía Suárez Avatar answered Oct 17 '25 20:10

Luis Miguel Mejía Suárez



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!