【问题标题】:Understanding the use of path dependent types to emulate existentially quantified types了解使用路径依赖类型来模拟存在量化类型
【发布时间】: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 -&gt; StringMonoid)?在def many[F[_]](pairs: (any[F]#Member, any[F]#Ops)*) 中,是什么限制了元组类型中any[F] 的两个出现以引用相同的类型?我应该从哪里(在 Scala 语言规范或其他任何地方)开始阅读才能从第一原则理解这一点?

【问题讨论】:

    标签: scala types existential-type scala-3 path-dependent-type


    【解决方案1】:

    因为你有这个隐式转换

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

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

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

    【讨论】:

    • 感谢您的最后评论。您已经向我指出,如果 F 是协变的(如 Option),那么我将失去我想要的约束。我可能不得不寻找不同的方法。
    猜你喜欢
    • 2019-01-27
    • 1970-01-01
    • 2016-06-03
    • 2019-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-05
    • 1970-01-01
    相关资源
    最近更新 更多