了解使用路径相关类型来模拟存在量化类型

Understanding the use of path dependent types to emulate existentially quantified types

我一直在关注这个博客 post,试图了解如何在 Scala 3 中使用路径相关类型模拟存在量化类型:https://dev.to/raquo/existential-crisis-implementing-mapk-in-scala-3-2fo1

然后我做了下面的例子

首先我们定义幺半群:

trait Monoid[A]:
  val id: A
  def combine(l: A, r: A): A

object StringMonoid extends Monoid[String]:
  val id = ""
  def combine(l: String, r: String) = l + r

object AdditiveIntMonoid extends Monoid[Int]:
  val id = 0
  def combine(l: Int, r: Int) = l + r

object MultiplicativeIntMonoid extends Monoid[Int]:
  val id = 1
  def combine(l: Int, r: Int) = r * r

现在假设我要编写的代码可以采用一组可能不都具有相同基础类型的幺半群。例如

def ids(ms: Monoid*) =        // This won't compile because Monoid with no argument 
  for { m <- ms } yield m.id           //  is not a type

def asList(pairs: ((E, Monoid[E]) for any E)*) =   // This is also not valid scala
  pairs.toList

我可以通过一些工作,按照博客中的模式实现我想要的行为。

首先定义一个内部类型依赖于路径的类型来模拟forSome:

type any[F[_]] = {
  type Member;
  type Ops = F[Member]
}

和一些命名不当的隐式转换来帮助我创建相关类型的实例:

given any_algebra[F[_], A]: Conversion[F[A], any[F]#Ops] = _.asInstanceOf[any[F]#Ops]
given any_algebra_with_member[F[_], A]: Conversion[(A, F[A]), (any[F]#Member, any[F]#Ops)] =
    _.asInstanceOf[(any[F]#Member, any[F]#Ops)]

现在我可以写了

def ids(ms: List[any[Monoid]#Ops]) =
  for { m <- ms } yield m.id

def all[F[_]](fs: any[F]#Ops*) = fs.toList

val ms = all(StringMonoid, MultiplicativeIntMonoid, AdditiveIntMonoid, StringMonoid)

val units = ids(all(AdditiveIntMonoid, StringMonoid, MultiplicativeIntMonoid))

def many[F[_]](pairs: (any[F]#Member, any[F]#Ops)*) = pairs.toList

val mms = many(
  7 -> AdditiveIntMonoid,
  3 -> MultiplicativeIntMonoid,
  "foo" -> StringMonoid,
  "bar" -> StringMonoid
)

一切正常。我不会写

val ms = all(StringMonoid, "hello", AdditiveIntMonoid, StringMonoid)

因为"hello"不是幺半群,或者

val mms = many(
  "foo" -> StringMonoid,
  3 -> StringMonoid
)

因为 3 不是字符串。

我的问题是为什么这最后一部分按我想要的方式工作。为什么不能我写many(3 -> StringMonoid)?在 def many[F[_]](pairs: (any[F]#Member, any[F]#Ops)*) 中,是什么限制了 any[F] 在元组类型中的两次出现都指向同一类型?我应该从哪里开始阅读(在 Scala 语言规范中或其他任何地方)以便能够从第一原则理解这一点?

因为你有这个隐式转换

Conversion[(A, F[A]), (any[F]#Member, any[F]#Ops)]

many 接受 (any[F]#Member, any[F]#Ops) 的元组。因此,当您提供元组 "foo" -> StringMonoid 时,编译器使用该隐式转换将其转换为请求类型的元组。但该转换仅适用于符合 (A, F[A]) 形状的元组。 IE。两个成员中的 A 必须是同一类型。但是当你提供3 -> StringMonoid时,左边的AInt,右边的AString,所以隐式转换将不起作用。

编译器可能仍会尝试通过推断 A = Any 使其工作,但 Monoid[String] 不是 Monoid[Any] 的子类型,因为它是不变的。所以那也不行。