我正在嘗試創建一種模式型別,它可以讓您以通用的、完全型別化的方式描述 Scala 型別。我有這個的產品和副產品版本,現在我正在嘗試使用 Scala 3 的鏡像來派生它們。
我目前面臨的特殊挑戰是從MirroredElemLabels型別中提取元素名稱Mirror.我的理解是這些型別是單例型別,可以使用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's,這些inline方法使用constValue或提取標簽值的方法ValueOf。我已經能夠使這些作業,(a)我需要能夠在型別類實體中這樣做,并且(b)我很好奇為什么我自己的方法不起作用!
為了更清楚我的用例,我提出的模式型別將副產品的子型別編碼為 的元組Subtype[T, ST, N <: String & Singleton, S],其中T是超型別的型別,ST是子型別的型別,N是子型別名稱的窄型別,S是子型別自己模式的窄型別。我希望能夠使用given型別類實體派生這個元組。
更新 2
Thanks to Mateusz's suggestion, I have been able to get the following version to compile...
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
}
However, the above fails the following test case:
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
}
If I add the following evidence to the using parameter list in mirDeriver: ev : NotGiven[ ElemLabels =:= EmptyTuple ], I get the following compilation error:
But no implicit values were found that match type util.NotGiven[? <: Tuple =:= EmptyTuple].
This suggests that the Mirror has and empty tuple for MirroredElemLabels. But again, I was able to confirm for the same test case that I could summon a mirror whose MirroredElemLabels type is ("SubT1", "SubtT2"). Not only that, but in the same compilation error that says there is no such NotGiven instance, it reports a given Mirror instance with:
{
MirroredElemTypes = (NamesDeriverTest.this.SubT1,
NamesDeriverTest.this.SubT2
); MirroredElemLabels = (("SubT1" : String), ("SubT2" : String))
}
What is going on here?? The plot thickens...
uj5u.com熱心網友回復:
當我需要這個功能時,我只是寫了一個實用程式來實作這一點,它使用ValueOf(這類似于WitnessShapeless 但內置):
// 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
}
uj5u.com熱心網友回復:
好的,我想出了一個解決方法!而不是引數化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$4@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。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/393593.html
標籤:scala metaprogramming scala-3
下一篇:當我使用com.databricks.spark.avro時,為什么必須添加org.apache.spark.avro依賴項才能在Spark2.4中讀/寫avro檔案?
