问题描述
我正在尝试但未能在 Scala 3 中实现这样的功能:
type TupleK[K[*],V[*],A] = (K[A],V[A])
final class MapK[K[*],V[*]] private (val rawMap: Map[K[?],V[?]]) {
def foreach(f: TupleK[K,V,?] => Unit): Unit = {
rawMap.foreach(f.asInstanceOf[Tuple2[K[?],V[?]] => Any])
}
}
object MapK {
def apply[K[*],V[*]](entries: TupleK[K,?]*): MapK[K,V] = {
new MapK[K,V](Map(entries: _*))
}
}
这样的用法:
class Key[A]()
type Id[A] = A
val intKey = Key[Int]
val strKey = Key[String]
MapK[Key,Id](intKey -> 1,strKey -> "a")
在有效的 Scala 2 中,只需要通过将 *
和 ?
替换为 _
来调整语法(当然 _*
除外)。
然而,在 Scala 3 中,基本上每一行都会出错,并带有“对通配符参数的高级类型的不可简化的应用”:Scastie。
文档说存在类型在 Scala 3 中一直是 dropped,但是他们并没有真正给出如何处理这个问题的任何非平凡的例子。
文档提到“存在类型与路径依赖类型在很大程度上重叠”——这个 MapK 可以用路径依赖类型实现吗?我已阅读 this,但不明白如何应用它,或者在我的情况下是否可行。
而且,如果不是路径依赖类型……那又如何? Scala 似乎不太可能被“简化”到无法再实现此功能的地步,所以我一定遗漏了一些东西。
ETA:除了我自己在下面的回答之外,我还制作了 this repo 并写了 this article 关于在 Scala 3 中编码 MapK 的各种方法。
解决方法
我能够产生一个有效的实现,尽管非常烦人。 This pointer 特别有价值。
首先说明几点:
-
这方面的类型推断在很多层面上都很糟糕。测试中的所有手动类型归属,以及下面的所有隐式转换都是需要的。
-
显然 Scala 不够聪明,无法在查找隐式时发现
A
和type Id[A] = A
在功能上是相同的,因此需要对特定隐式转换进行组合爆炸。丑陋且可扩展性不强。 -
观察 Scala 3 中可用的不同选项:
foreach
、foreachT
和foreachK
。所有这些都有风格上的权衡。 -
如果您可以改进其中任何一项,请告诉我。这有效,但它在 Scala 2 中要好得多。
MapK 实现:
class MapK[K[_],V[_]] protected(protected val rawMap: Map[Type[K],Type[V]]) {
def apply[A](key: K[A]): V[A] = {
rawMap(key).asInstanceOf[V[A]]
}
def updated[A](key: K[A],value: V[A]): MapK[K,V] = {
MapK.unsafeCoerce(rawMap.updated(key,value))
}
def updated[A](pair: (K[A],V[A])): MapK[K,V] = {
MapK.unsafeCoerce(rawMap.updated(pair._1,pair._2))
}
def foreach[A](f: ((K[A],V[A])) => Unit): Unit = {
rawMap.foreach(f.asInstanceOf[(([Type[K],Type[V]])) => Any])
}
def foreachT(f: Type.Tuple2[K,V] => Unit): Unit = {
foreach { (k,v) => f((k,v)) }
}
def foreachK(f: [A] => (K[A],V[A]) => Unit): Unit = {
foreach { (k,v) => f(k,v) }
}
}
object MapK {
def unsafeCoerce[K[_],V[_]](rawMap: Map[Type[K],Type[V]]): MapK[K,V] = {
new MapK[K,V](rawMap)
}
def apply[K[_],V[_]](entries: Type.Tuple2[K,V]*): MapK[K,V](Map(entries.asInstanceOf[Seq[(Type[K],Type[V])]]: _*))
}
}
您可能希望在 MapK 中实现的其他方法基本上遵循与 foreach
、foreachT
或 foreachK
相同的模式。
现在,用法:
def test(caption: String)(code: => Unit): Unit = code
def assertEquals[A](a: A,b: A): Unit = assert(a == b)
case class Key[A](label: String,default: A)
val boolKey = Key[Boolean]("bool",false)
val intKey = Key[Int]("int",0)
val strKey = Key[String]("str","")
val optionMap = MapK[Key,Option](boolKey -> Some(true),intKey -> Some(1),strKey -> Option("a"),strKey -> None)
val idMap = MapK[Key,Id](boolKey -> true,intKey -> 1,strKey -> "hello")
val expectedOptionValues = List[Type.Tuple3[Key,Option,Id]](
(boolKey,Some(true),false),(intKey,Some(1),0),(strKey,None,"")
)
val expectedIdValues = List[Type.Tuple3[Key,Id,true,1,"hello","")
)
test("optionMap - apply & updated") {
assertEquals(optionMap(intKey),Some(1))
assertEquals(optionMap(strKey),None)
assertEquals(optionMap.updated(strKey,Some("yo"))(strKey),Some("yo"))
}
test("optionMap - foreach") {
var values: List[Type.Tuple3[Key,Id]] = Nil
optionMap.foreach { (k,v) =>
values = values :+ (k,v,k.default)
}
assertEquals(values,expectedOptionValues)
}
test("optionMap - foreachT") {
var values: List[Type.Tuple3[Key,Id]] = Nil
optionMap.foreachT { pair => // no parameter untupling :(
values = values :+ (pair._1,pair._2,pair._1.default)
}
assertEquals(values,expectedOptionValues)
}
test("optionMap - foreachK") {
var values: List[Type.Tuple3[Key,Id]] = Nil
optionMap.foreachK {
[A] => (k: Key[A],v: Option[A]) => // need explicit types :(
values = values :+ (k,expectedOptionValues)
}
test("idMap - apply & updated") {
assertEquals(idMap(intKey),1)
assertEquals(idMap(strKey),"hello")
assertEquals(idMap.updated(strKey,"yo")(strKey),"yo")
}
test("idMap - foreach") {
var values: List[Type.Tuple3[Key,Id]] = Nil
idMap.foreach { (k,expectedIdValues)
}
test("idMap - foreachT") {
var values: List[Type.Tuple3[Key,Id]] = Nil
idMap.foreachT { pair =>
values = values :+ (pair._1,expectedIdValues)
}
test("idMap - foreachK") {
var values: List[Type.Tuple3[Key,Id]] = Nil
idMap.foreachK {
[A] => (k: Key[A],v: A) =>
values = values :+ (k,expectedIdValues)
}
现在,使这项工作成功的支持演员:
import scala.language.implicitConversions // old style,but whatever
type Id[A] = A
type Type[F[_]] <: (Any { type T })
object Type {
type Tuple2[F[_],G[_]] <: (Any { type T })
type Tuple3[F[_],G[_],H[_]] <: (Any { type T })
}
implicit def wrap[F[_],A](value: F[A]): Type[F] =
value.asInstanceOf[Type[F]]
implicit def wrapT2[F[_],A](value: (F[A],G[A])): Type.Tuple2[F,G] =
value.asInstanceOf[Type.Tuple2[F,G]]
implicit def wrapT2_P1[F[_],A](t: (F[A],A)): Type.Tuple2[F,Id] = wrapT2[F,A](t)
implicit def wrapT3[F[_],H[_],G[A],H[A])): Type.Tuple3[F,G,H] =
value.asInstanceOf[Type.Tuple3[F,H]]
implicit def wrapT3_P1[F[_],A,A)): Type.Tuple3[F,Id] =
value.asInstanceOf[Type.Tuple3[F,Id]]
implicit def wrapT3_P1_P2[F[_],Id]]
implicit def unwrap[F[_]](value: Type[F]): F[value.T] =
value.asInstanceOf[F[value.T]]
implicit def unwrapT2[F[_],G[_]](value: Type.Tuple2[F,G]): (F[value.T],G[value.T]) =
value.asInstanceOf[(F[value.T],G[value.T])]
implicit def unwrapT3[F[_],H[_]](value: Type.Tuple3[F,H]): (F[value.T],G[value.T],H[value.T]) =
value.asInstanceOf[(F[value.T],H[value.T])]
,
这是使用依赖类型的替代解决方案。总的来说,我更喜欢它,我更清楚发生了什么。
import scala.language.implicitConversions
type Id[A] = A
implicit def wrapId[A](a: A): Id[A] = a
implicit def unwrapId[A](a: Id[A]): A = a
case class Key[A](caption: String,default: A)
val boolKey = Key[Boolean]("bool",false)
val intKey = Key[Int]("int",0)
val strKey = Key[String]("str","")
type KTuple[K[_],V[_]] = {
type T;
type Pair = (K[T],V[T]);
}
implicit def KTuple[K[_],V[_],A](value: (K[A],V[A])): KTuple[K,V]#Pair = value.asInstanceOf[KTuple[K,V]#Pair]
implicit def KTuple_P1[K[_],A)): KTuple[K,Id]#Pair = value.asInstanceOf[KTuple[K,Id]#Pair]
class MapK[K[_],V[_]](rawMap: Map[K[Any],V[Any]]) {
def foreachK(f: [A] => (K[A],V[A]) => Unit): Unit = {
rawMap.foreach(f.asInstanceOf[((K[Any],V[Any])) => Unit])
}
def foreach(f: KTuple[K,V]#Pair => Unit): Unit = {
rawMap.foreach { pair =>
f(pair.asInstanceOf[KTuple[K,V]#Pair])
}
}
}
object MapK {
def create[K[_],V[_]](pairs: KTuple[K,V]#Pair*): MapK[K,V] = {
val x: List[KTuple[K,V]#Pair] = pairs.toList
val y: List[(K[Any],V[Any])] = x.map(t => t.asInstanceOf[(K[Any],V[Any])])
new MapK(Map(y: _*))
}
}
val idMap = MapK.create[Key,Id](
boolKey -> false,strKey -> "a",)
val optionMap = MapK.create[Key,Option](
intKey -> Some(1),strKey -> Some("a")
)
type T3[A] = (Key[A],A)
var log = List[KTuple[Key,Option]#Pair]()
idMap.foreach { (k,v) =>
log = log.appended(KTuple(k,Some(v)))
}
def doSomething[A,V[_]](k: Key[A],v: V[A]): Unit = println(s"$k -> v")
optionMap.foreachK {
[A] => (k: Key[A],v: Option[A]) => {
doSomething(k,v.get)
doSomething(k,v)
log = log :+ KTuple((k,v))
}
}
我写了一篇包含更多细节的博客文章,经过一些编辑后会很快发布。不过仍在寻找更好的方法和改进。