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?
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".