【发布时间】:2021-11-13 11:15:47
【问题描述】:
我一直在关注这篇博文,试图了解如何在 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" 不是 Monoid,或者
val mms = many(
"foo" -> StringMonoid,
3 -> StringMonoid
)
因为3 不是字符串。
我的问题是为什么最后一部分可以按我的意愿工作。为什么不能我写many(3 -> StringMonoid)?在def many[F[_]](pairs: (any[F]#Member, any[F]#Ops)*) 中,是什么限制了元组类型中any[F] 的两个出现以引用相同的类型?我应该从哪里(在 Scala 语言规范或其他任何地方)开始阅读才能从第一原则理解这一点?
【问题讨论】:
标签: scala types existential-type scala-3 path-dependent-type