5

我试图让这样的东西在 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, V, ?]*): 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 中被删除,但是他们并没有真正给出如何处理这个问题的任何重要示例。

文档提到“存在类型在很大程度上与路径相关类型重叠”——这个 MapK 可以用路径相关类型实现吗?我读过这个,但不明白如何应用它,或者在我的情况下是否可能。

而且,如果不是路径依赖类型......那又如何?Scala 似乎不太可能“简化”到无法再实现此功能的地步,所以我一定遗漏了一些东西。

ETA:除了下面我自己的答案之外,我还制作了这个 repo并写了这篇关于在 Scala 3 中编码 MapK 的各种方法的文章。

4

2 回答 2

1

这是使用依赖类型的替代解决方案。总的来说,我更喜欢它,对我来说发生了什么更明显。

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](value: (K[A], 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,
  intKey -> 1,
  strKey -> "a",
)

val optionMap = MapK.create[Key, Option](
  intKey -> Some(1),
  strKey -> Some("a")
)

type T3[A] = (Key[A], 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))
  }
}

我写了一篇包含更多细节的博客文章,经过一些编辑后将很快发布。尽管如此,仍在寻找更好的方法和改进。

于 2021-06-05T11:01:40.953 回答
1

我能够产生一个有效的,尽管非常烦人的实现。这个指针特别有价值。

首先,几点注意事项:

  • 对此的类型推断在很多层面上都很糟糕。测试中的所有手动类型归属以及下面的所有隐式转换都需要它才能工作。

  • 显然 Scala 不够聪明,无法弄清楚这一点,A并且type Id[A] = A在寻找隐式时在功能上是相同的,因此需要临时隐式转换的组合爆炸。丑陋且不可扩展。

  • 观察 Scala 3 中可用的不同选项:foreachforeachTforeachK. 它们都有风格上的权衡。

  • 如果您可以改进其中的任何一个,请告诉我。这行得通,但在 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] = {
    new MapK[K, V](Map(entries.asInstanceOf[Seq[(Type[K], Type[V])]]: _*))
  }
}

您可能想要实现的 MapK 中的其他方法基本上遵循与 、 或 相同foreachforeachT模式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, Id]](
    (boolKey, true, false),
    (intKey, 1, 0),
    (strKey, "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, Option, Id]] = Nil

    optionMap.foreach { (k, v) =>
      values = values :+ (k, v, k.default)
    }

    assertEquals(values, expectedOptionValues)
  }

  test("optionMap - foreachT") {
    var values: List[Type.Tuple3[Key, Option, 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, Option, Id]] = Nil

    optionMap.foreachK {
      [A] => (k: Key[A], v: Option[A]) => // need explicit types :(
        values = values :+ (k, v, k.default)
    }

    assertEquals(values, 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, Id]] = Nil

    idMap.foreach { (k, v) =>
      values = values :+ (k, v, k.default)
    }

    assertEquals(values, expectedIdValues)
  }

  test("idMap - foreachT") {
    var values: List[Type.Tuple3[Key, Id, Id]] = Nil

    idMap.foreachT { pair =>
      values = values :+ (pair._1, pair._2, pair._1.default)
    }

    assertEquals(values, expectedIdValues)
  }

  test("idMap - foreachK") {
    var values: List[Type.Tuple3[Key, Id, Id]] = Nil

    idMap.foreachK {
      [A] => (k: Key[A], v: A) =>
        values = values :+ (k, v, k.default)
    }

    assertEquals(values, 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[_], G[_], 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, Id, A](t)

implicit def wrapT3[F[_], G[_], H[_], A](value: (F[A], G[A], H[A])): Type.Tuple3[F, G, H] =
  value.asInstanceOf[Type.Tuple3[F, G, H]]

implicit def wrapT3_P1[F[_], G[_], A](value: (F[A], A, A)): Type.Tuple3[F, Id, Id] =
  value.asInstanceOf[Type.Tuple3[F, Id, Id]]

implicit def wrapT3_P1_P2[F[_], G[_], A](value: (F[A], G[A], A)): Type.Tuple3[F, G, Id] =
  value.asInstanceOf[Type.Tuple3[F, G, 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[_], G[_], H[_]](value: Type.Tuple3[F, G, H]): (F[value.T], G[value.T], H[value.T]) =
  value.asInstanceOf[(F[value.T], G[value.T], H[value.T])]
于 2021-06-04T08:25:01.180 回答