我一直在看这篇博文,试图理解如何在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
现在假设我要编写一段代码,它可以接受一组monoids,这些monoids可能不都具有相同的底层类型。例如
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])
形状的元组。即两个成员中的A
必须是相同的类型。但是当您提供3 -> StringMonoid
时,左边的A
是Int
,右边的A
是String
,因此隐式转换将不起作用。
编译器可能仍然试图通过推断A = Any
使其工作,但Monoid[String]
不是Monoid[Any]
的子类型,因为它是不变的。那也不行