首页 > 解决方案 > 如何在 Scala 3 / Dotty 中实现 MapK 之类的类型?

问题描述

我试图让这样的东西在 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 的各种方法的文章。

标签: scalaexistential-typehigher-kinded-typesscala-3dotty

解决方案


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

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))
  }
}

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


推荐阅读