【发布时间】:2022-01-21 05:53:32
【问题描述】:
我正在尝试创建一种模式类型,它可以让您以通用的、完全类型化的方式描述 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")]
我应该能够提取具有以下类型类的值:
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)我需要能够在类型类实例中这样做,并且(b)我很好奇为什么我自己的方法不起作用!
为了更清楚地了解我的用例,我提出的模式类型将联产品的子类型编码为Subtype[T, ST, N <: String & Singleton, S] 的元组,其中T 是超类型的类型,ST 是子类型的类型, N 是子类型名称的窄类型,S 是子类型自身架构的窄类型。我希望能够使用given 类型类实例派生这个元组。
更新 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))
}
这是怎么回事??剧情变厚了……
【问题讨论】:
-
您必须在使用
inline宏派生类型类时执行此操作。这意味着您不能创建类型类转到值级别,并要求编译时反射。相反,您可以设计您的类型类,以便在构造它时嵌入有关其总和/乘积元素的知识(使用inline),然后在值级别上您只需调用类型类的实例。有关示例,请参见此处github.com/MateuszKubuszok/dbg/blob/… -
换句话说,如果您希望能够使用编译时反射和/或重新设计
NamesDeriver,则必须将您的def getNames重写为inline以便它在编译时构造值时间,然后在这些新实例中返回常量。 -
我认为这不可能与
constValue有关。constValue["a"]有效,但val a = "a"; constValue[a.type]无效,这让我相信 any 类型的标识符不会被编译器视为常量类型。我觉得通过这些类型访问命名信息似乎是为了宏。 -
@MichaelZajac 我肯定在网上看到过使用
constValue从镜像中获取元素标签的工作示例,所以我很确定它可以工作。见,例如blog.oyanglul.us/scala/dotty/en/generic-type-class-derivation -
我认为有一个错误,因为我已经能够从
Product镜像中提取元素标签,但不能从Sums 中提取元素标签。在后一种情况下,编译器似乎无法正确推断出ElemLabels参数。我在这里报告了这个问题:github.com/lampepfl/dotty/issues/14150.
标签: scala metaprogramming scala-3