Scala 3:如何从 Mirror.Sum 中提取元素名称作为元组?
Scala 3: How do you extract the names of elements from a Mirror.Sum as a tuple?
我正在尝试创建一个模式类型,它可以让您以通用的、完全类型化的方式描述 Scala 类型。我有它的积和联积版本,现在我正尝试使用 Scala 3 的镜像来导出它们。
我目前面临的特殊挑战是从 Mirror.
中的 MirroredElemLabels
类型中提取元素名称 我的理解是这些类型是单例类型,可以转换为它们的单例值使用 scala.compiletime.constValue.
我可以确认 MirroredElemLabels
是我在以下测试用例中所期望的:
sealed trait SuperT
final case class SubT1( int : Int ) extends SuperT
final case class SubT2( str : String ) extends SuperT
val mirror = summon[Mirror.SumOf[SuperT]]
summon[mirror.MirroredElemLabels =:= ("SubT1", "SubT2")]
我应该能够提取具有以下类型的值 class:
import scala.deriving.Mirror
import scala.compiletime.constValue
trait NamesDeriver[ T ] {
type Names <: Tuple
def derive : Names
}
object NamesDeriver {
type Aux[ T, Ns ] = NamesDeriver[ T ] { type Names = Ns }
inline given mirDeriver[ T, ELs <: Tuple ](
using
mir : Mirror.Of[ T ] { type MirroredElemLabels = ELs },
der : NamesDeriver[ ELs ],
) : NamesDeriver[ T ] with {
type Names = der.Names
def derive : der.Names = der.derive
}
given emptyDeriver : NamesDeriver[ EmptyTuple ] with {
type Names = EmptyTuple
def derive : EmptyTuple = EmptyTuple
}
inline given labelsDeriver[ N <: String & Singleton, Tail <: Tuple ](
using
next : NamesDeriver.Aux[ Tail, Tail ],
) : NamesDeriver[ N *: Tail ] with {
type Names = N *: Tail
def derive : N *: Tail = constValue[ N ] *: next.derive
}
def getNames[ T ](
using
nd : NamesDeriver[ T ],
) : nd.Names = nd.derive
}
但这不会编译:
not a constant type: labelsDeriver.this.N; cannot take constValue
为什么我不能在这里使用 constValue
?
更新
我看到了几种方法,包括下面的 Mateusz Kubuszok 的方法,它们使用 inline
方法使用 constValue
或 ValueOf
提取标签值。我已经能够完成这些工作,(a) 我需要能够在类型 class 实例中这样做,并且 (b) 我很好奇为什么我自己的方法不起作用!
为了更清楚地说明我的用例,我提出的模式类型将副产品的子类型编码为 Subtype[T, ST, N <: String & Singleton, S]
的元组,其中 T
是超类型的类型,ST
是子类型的类型,N
是子类型名称的窄类型,S
是子类型自己模式的窄类型。我希望能够使用 given
类型 class 实例派生此元组。
更新 2
感谢 Mateusz 的建议,我已经能够得到以下版本来编译...
import scala.deriving.Mirror
import scala.util.NotGiven
import scala.compiletime.{constValue, erasedValue, summonAll, summonInline}
trait Deriver {
type Derived
def derive : Derived
}
trait MirrorNamesDeriver[ T ] extends Deriver { type Derived <: Tuple }
object MirrorNamesDeriver {
type Aux[ T, Ns <: Tuple ] = MirrorNamesDeriver[ T ] {type Derived = Ns}
// def values(t: Tuple): Tuple = t match
// case (h: ValueOf[_]) *: t1 => h.value *: values(t1)
// case EmptyTuple => EmptyTuple
inline given mirDeriver[ T, ElemLabels <: Tuple, NDRes <: Tuple ](
using
mir: Mirror.SumOf[ T ] {type MirroredElemLabels = ElemLabels},
nd: NamesDeriver.Aux[ ElemLabels, ElemLabels ],
): MirrorNamesDeriver.Aux[ T, ElemLabels ] = {
new MirrorNamesDeriver[ T ] {
type Derived = ElemLabels
def derive: ElemLabels = nd.derive
}
}
}
trait NamesDeriver[ R ] extends Deriver
object NamesDeriver {
type Aux[ R, D ] = NamesDeriver[ R ] { type Derived = D }
inline given emptyDeriver : NamesDeriver[ EmptyTuple ] with {
type Derived = EmptyTuple
def derive : EmptyTuple = EmptyTuple
}
inline given labelsDeriver[ N <: (String & Singleton), Tail <: Tuple ](
using
next : NamesDeriver.Aux[ Tail, Tail ],
) : NamesDeriver.Aux[ N *: Tail, N *: Tail ] = {
val derivedValue = constValue[ N ] *: next.derive
new NamesDeriver[ N *: Tail ] {
type Derived = N *: Tail
def derive : N *: Tail = derivedValue
}
}
inline def getNames[ T ](
using
nd : MirrorNamesDeriver[ T ],
) : nd.Derived = nd.derive
}
但是,上面的测试用例失败了:
sealed trait SuperT
final case class SubT1( int : Int ) extends SuperT
final case class SubT2( str : String ) extends SuperT
"NamesDeriver" should "derive names from a coproduct" in {
val nms = NamesDeriver.getNames[ SuperT ]
nms.size shouldBe 2
}
如果我将以下证据添加到 mirDeriver
中的 using
参数列表:ev : NotGiven[ ElemLabels =:= EmptyTuple ]
,我会得到以下编译错误:
But no implicit values were found that match type util.NotGiven[? <: Tuple =:= EmptyTuple].
这表明 Mirror
具有 MirroredElemLabels
的空元组。但同样,对于同一个测试用例,我能够确认我可以召唤一个 MirroredElemLabels
类型为 ("SubT1", "SubtT2")
的镜像。不仅如此,在 same 编译错误中,表示没有这样的 NotGiven
实例,它报告给定的 Mirror
实例:
{
MirroredElemTypes = (NamesDeriverTest.this.SubT1,
NamesDeriverTest.this.SubT2
); MirroredElemLabels = (("SubT1" : String), ("SubT2" : String))
}
这是怎么回事??剧情变厚了...
当我需要此功能时,我只是编写了一个实用程序来实现此目的,它使用 ValueOf
(这类似于来自 Shapeless 的 Witness
,但内置):
// T is m.MirroredElemLabels - tuple of singleton types describing labels
inline def summonLabels[T <: Tuple]: List[String] =
inline erasedValue[T] match
case _: EmptyTuple => Nil
case _: (t *: ts) => summonInline[ValueOf[t]].value.asInstanceOf[String] :: summonLabels[ts]
val labels = summonLabels[p.MirroredElemLabels]
但您可能可以使用类似
的代码以更少的代码实现它
// 1. turn type (A, B, ...) into type (ValueOf[A], ValueOf[B], ...)
// (for MirroredElemLabels A, B, ... =:= String)
// 2. for type (ValueOf[A], ValueOf[B], ...) summon List[ValueOf[A | B | ...]]
// (which should be a List[ValueOf[String]] but if Scala
// gets confused about this you can try `.asInstanceOf`)
// 3. turn it into a List[String]
summonAll[Tuple.Map[p.MirroredElemLabels, ValueOf]]
.map(valueOf => valueOf.value.asInstanceOf[String])
编辑:
尝试将您的代码重写为
inline given labelsDeriver[ N <: String & Singleton, Tail <: Tuple ](
using
next : NamesDeriver.Aux[ Tail, Tail ],
) : NamesDeriver[ N *: Tail ] =
// makes sure value is computed before instance is constructed
val precomputed = constValue[ N ] *: next.derive
new NamesDeriver[ N *: Tail ] {
type Names = N *: Tail
// apparently, compiler thinks that you wanted to put
// constValue resolution into new instance's method body
// rather than within macro, which is why it fails
// so try to force it to compute it in compile-time
def derive : N *: Tail = precomputed
}
好的,我找到了解决方法!与其参数化 Mirror
的 MirroredElemLabels
类型以将 NamesDeriver
作为第二个上下文参数包含在 mirDeriver
中,我们可以只使用 summonInline
来联想内联给定定义中的 NamesDeriver
:
transparent inline given mirDeriver[ T ](
using
mir: Mirror.SumOf[ T ],
): MirrorNamesDeriver.Aux[ T, mir.MirroredElemLabels ] = {
val namesDeriver = summonInline[ NamesDeriver.Aux[ mir.MirroredElemLabels, mir.MirroredElemLabels ] ]
new MirrorNamesDeriver[ T ] {
type Derived = mir.MirroredElemLabels
def derive: mir.MirroredElemLabels = namesDeriver.derive
}
}
添加 transparent
有助于我的 IDE 识别结果类型,但它似乎与编译无关。这是测试用例的结果:
val deriver = summon[MirrorNamesDeriver[ SuperT ]]
summon[deriver.Derived =:= ("SubT1", "SubT2")]
val nms = MirrorNamesDeriver.getNames[ SuperT ]
println(nms.size)
...输出:
val deriver: MirrorNamesDeriver[SuperT]{Derived = ("SubT1", "SubT2")} = anon@79d56038
val res0: ("SubT1", "SubT2") =:= ("SubT1", "SubT2") = generalized constraint
val nms: ("SubT1", "SubT2") = (SubT1,SubT2)
2
更新
事实证明,只需使用通过上下文参数调用的类型 类 就可以做到这一点。参见 https://github.com/lampepfl/dotty/issues/14150#issuecomment-998586254。
我正在尝试创建一个模式类型,它可以让您以通用的、完全类型化的方式描述 Scala 类型。我有它的积和联积版本,现在我正尝试使用 Scala 3 的镜像来导出它们。
我目前面临的特殊挑战是从 Mirror.
中的 MirroredElemLabels
类型中提取元素名称 我的理解是这些类型是单例类型,可以转换为它们的单例值使用 scala.compiletime.constValue.
我可以确认 MirroredElemLabels
是我在以下测试用例中所期望的:
sealed trait SuperT
final case class SubT1( int : Int ) extends SuperT
final case class SubT2( str : String ) extends SuperT
val mirror = summon[Mirror.SumOf[SuperT]]
summon[mirror.MirroredElemLabels =:= ("SubT1", "SubT2")]
我应该能够提取具有以下类型的值 class:
import scala.deriving.Mirror
import scala.compiletime.constValue
trait NamesDeriver[ T ] {
type Names <: Tuple
def derive : Names
}
object NamesDeriver {
type Aux[ T, Ns ] = NamesDeriver[ T ] { type Names = Ns }
inline given mirDeriver[ T, ELs <: Tuple ](
using
mir : Mirror.Of[ T ] { type MirroredElemLabels = ELs },
der : NamesDeriver[ ELs ],
) : NamesDeriver[ T ] with {
type Names = der.Names
def derive : der.Names = der.derive
}
given emptyDeriver : NamesDeriver[ EmptyTuple ] with {
type Names = EmptyTuple
def derive : EmptyTuple = EmptyTuple
}
inline given labelsDeriver[ N <: String & Singleton, Tail <: Tuple ](
using
next : NamesDeriver.Aux[ Tail, Tail ],
) : NamesDeriver[ N *: Tail ] with {
type Names = N *: Tail
def derive : N *: Tail = constValue[ N ] *: next.derive
}
def getNames[ T ](
using
nd : NamesDeriver[ T ],
) : nd.Names = nd.derive
}
但这不会编译:
not a constant type: labelsDeriver.this.N; cannot take constValue
为什么我不能在这里使用 constValue
?
更新
我看到了几种方法,包括下面的 Mateusz Kubuszok 的方法,它们使用 inline
方法使用 constValue
或 ValueOf
提取标签值。我已经能够完成这些工作,(a) 我需要能够在类型 class 实例中这样做,并且 (b) 我很好奇为什么我自己的方法不起作用!
为了更清楚地说明我的用例,我提出的模式类型将副产品的子类型编码为 Subtype[T, ST, N <: String & Singleton, S]
的元组,其中 T
是超类型的类型,ST
是子类型的类型,N
是子类型名称的窄类型,S
是子类型自己模式的窄类型。我希望能够使用 given
类型 class 实例派生此元组。
更新 2
感谢 Mateusz 的建议,我已经能够得到以下版本来编译...
import scala.deriving.Mirror
import scala.util.NotGiven
import scala.compiletime.{constValue, erasedValue, summonAll, summonInline}
trait Deriver {
type Derived
def derive : Derived
}
trait MirrorNamesDeriver[ T ] extends Deriver { type Derived <: Tuple }
object MirrorNamesDeriver {
type Aux[ T, Ns <: Tuple ] = MirrorNamesDeriver[ T ] {type Derived = Ns}
// def values(t: Tuple): Tuple = t match
// case (h: ValueOf[_]) *: t1 => h.value *: values(t1)
// case EmptyTuple => EmptyTuple
inline given mirDeriver[ T, ElemLabels <: Tuple, NDRes <: Tuple ](
using
mir: Mirror.SumOf[ T ] {type MirroredElemLabels = ElemLabels},
nd: NamesDeriver.Aux[ ElemLabels, ElemLabels ],
): MirrorNamesDeriver.Aux[ T, ElemLabels ] = {
new MirrorNamesDeriver[ T ] {
type Derived = ElemLabels
def derive: ElemLabels = nd.derive
}
}
}
trait NamesDeriver[ R ] extends Deriver
object NamesDeriver {
type Aux[ R, D ] = NamesDeriver[ R ] { type Derived = D }
inline given emptyDeriver : NamesDeriver[ EmptyTuple ] with {
type Derived = EmptyTuple
def derive : EmptyTuple = EmptyTuple
}
inline given labelsDeriver[ N <: (String & Singleton), Tail <: Tuple ](
using
next : NamesDeriver.Aux[ Tail, Tail ],
) : NamesDeriver.Aux[ N *: Tail, N *: Tail ] = {
val derivedValue = constValue[ N ] *: next.derive
new NamesDeriver[ N *: Tail ] {
type Derived = N *: Tail
def derive : N *: Tail = derivedValue
}
}
inline def getNames[ T ](
using
nd : MirrorNamesDeriver[ T ],
) : nd.Derived = nd.derive
}
但是,上面的测试用例失败了:
sealed trait SuperT
final case class SubT1( int : Int ) extends SuperT
final case class SubT2( str : String ) extends SuperT
"NamesDeriver" should "derive names from a coproduct" in {
val nms = NamesDeriver.getNames[ SuperT ]
nms.size shouldBe 2
}
如果我将以下证据添加到 mirDeriver
中的 using
参数列表:ev : NotGiven[ ElemLabels =:= EmptyTuple ]
,我会得到以下编译错误:
But no implicit values were found that match type util.NotGiven[? <: Tuple =:= EmptyTuple].
这表明 Mirror
具有 MirroredElemLabels
的空元组。但同样,对于同一个测试用例,我能够确认我可以召唤一个 MirroredElemLabels
类型为 ("SubT1", "SubtT2")
的镜像。不仅如此,在 same 编译错误中,表示没有这样的 NotGiven
实例,它报告给定的 Mirror
实例:
{
MirroredElemTypes = (NamesDeriverTest.this.SubT1,
NamesDeriverTest.this.SubT2
); MirroredElemLabels = (("SubT1" : String), ("SubT2" : String))
}
这是怎么回事??剧情变厚了...
当我需要此功能时,我只是编写了一个实用程序来实现此目的,它使用 ValueOf
(这类似于来自 Shapeless 的 Witness
,但内置):
// T is m.MirroredElemLabels - tuple of singleton types describing labels
inline def summonLabels[T <: Tuple]: List[String] =
inline erasedValue[T] match
case _: EmptyTuple => Nil
case _: (t *: ts) => summonInline[ValueOf[t]].value.asInstanceOf[String] :: summonLabels[ts]
val labels = summonLabels[p.MirroredElemLabels]
但您可能可以使用类似
的代码以更少的代码实现它// 1. turn type (A, B, ...) into type (ValueOf[A], ValueOf[B], ...)
// (for MirroredElemLabels A, B, ... =:= String)
// 2. for type (ValueOf[A], ValueOf[B], ...) summon List[ValueOf[A | B | ...]]
// (which should be a List[ValueOf[String]] but if Scala
// gets confused about this you can try `.asInstanceOf`)
// 3. turn it into a List[String]
summonAll[Tuple.Map[p.MirroredElemLabels, ValueOf]]
.map(valueOf => valueOf.value.asInstanceOf[String])
编辑:
尝试将您的代码重写为
inline given labelsDeriver[ N <: String & Singleton, Tail <: Tuple ](
using
next : NamesDeriver.Aux[ Tail, Tail ],
) : NamesDeriver[ N *: Tail ] =
// makes sure value is computed before instance is constructed
val precomputed = constValue[ N ] *: next.derive
new NamesDeriver[ N *: Tail ] {
type Names = N *: Tail
// apparently, compiler thinks that you wanted to put
// constValue resolution into new instance's method body
// rather than within macro, which is why it fails
// so try to force it to compute it in compile-time
def derive : N *: Tail = precomputed
}
好的,我找到了解决方法!与其参数化 Mirror
的 MirroredElemLabels
类型以将 NamesDeriver
作为第二个上下文参数包含在 mirDeriver
中,我们可以只使用 summonInline
来联想内联给定定义中的 NamesDeriver
:
transparent inline given mirDeriver[ T ](
using
mir: Mirror.SumOf[ T ],
): MirrorNamesDeriver.Aux[ T, mir.MirroredElemLabels ] = {
val namesDeriver = summonInline[ NamesDeriver.Aux[ mir.MirroredElemLabels, mir.MirroredElemLabels ] ]
new MirrorNamesDeriver[ T ] {
type Derived = mir.MirroredElemLabels
def derive: mir.MirroredElemLabels = namesDeriver.derive
}
}
添加 transparent
有助于我的 IDE 识别结果类型,但它似乎与编译无关。这是测试用例的结果:
val deriver = summon[MirrorNamesDeriver[ SuperT ]]
summon[deriver.Derived =:= ("SubT1", "SubT2")]
val nms = MirrorNamesDeriver.getNames[ SuperT ]
println(nms.size)
...输出:
val deriver: MirrorNamesDeriver[SuperT]{Derived = ("SubT1", "SubT2")} = anon@79d56038
val res0: ("SubT1", "SubT2") =:= ("SubT1", "SubT2") = generalized constraint
val nms: ("SubT1", "SubT2") = (SubT1,SubT2)
2
更新
事实证明,只需使用通过上下文参数调用的类型 类 就可以做到这一点。参见 https://github.com/lampepfl/dotty/issues/14150#issuecomment-998586254。