【问题标题】:What cause that type mismatch with underlying type in Scala是什么导致该类型与 Scala 中的基础类型不匹配
【发布时间】:2021-07-30 04:16:27
【问题描述】:

我有以下代码:

trait M[A] {
  def unit(a: A): M[A]
  def ★[B](f: A => M[B]): M[B]
}

case class State[A, S](run: S => (A, S)) extends M[A] {
  def unit[S](a: A): M[A] = State((s: S) => (a, s))
  def ★[B, S](f: A => M[B]): M[B] = State((s0: S) => {
    val (a, s1) = this.run(s0)
    val (b, s2) = f(a)(s1)
    (b, s2)
  })
}

但我明白了:

error: type mismatch;
 found   : s0.type (with underlying type S)
 required: S
    val (a, s1) = this.run(s0)
                           ^
error: type mismatch;
 found   : a.type (with underlying type Any)
 required: A
    val (b, s2) = f(a)(s1)
                    ^
error: type mismatch;
 found   : b.type (with underlying type Any)
 required: B
    (b, s2)
     ^
three errors found

是否意味着我不能在unit和★的定义中混合抽象类型A、B、S和case类实例,而应该使用trait State[A] extends M[A] {...}来代替?

【问题讨论】:

  • def unit[S]() 中的类型参数Scase class State[A, S]() 中的S 不同。 def *[B, S]() 中的 S 也不是。
  • @LeoC 是的,确实,我可以删除单元中的 S [S] 和 ★[B, S] 它们不是必需的,但我得到另一个错误。

标签: scala class types traits


【解决方案1】:

这个例子可能会被强制编译(参见第 2 部分),但我认为将其与使用类型类的标准方法进行比较更有价值,并理解为什么尝试修复这种方法会导致如此多的结果,而不是这样做麻烦。

与标准方法的比较

回想一下,使用类型类对 state-monad 建模看起来有点像这样:

trait Monad[F[_]]:
  def pure[A](a: => A): F[A]
  def flatMap[A, B](a: F[A], f: A => F[B]): F[B]

case class State[S, A](run: S => (S, A)):
  def flatMap[B](f: A => State[S, B]): State[S, B] = State(s0 =>
    val (s1, a) = this.run(s0)                                                           
    f(a).run(s1)                                              
  )

object State:
  given stateMonad[S]: Monad[[X] =>> State[S, X]] with
    def pure[A](a: => A): State[S, A] = State(s => (s, a))
    def flatMap[A, B](a: State[S, A], f: A => State[S, B]): State[S, B] =
      a.flatMap(f)

请注意,这里有三个不同的部分:

  • Monad[F]-typeclass,基本对应语句“F是一个monad”,参数化为F
  • State,一个单独的数据结构关于它我们正在发表声明
  • implicit/givenstateMonad,这是“对于所有S[X] => State[S, X] 是一个单子”陈述的建设性证明。

请注意,Monad 部分(声明)与 F 部分(我们正在对其进行声明的对象)严格分开。

综合起来,这三个部分可以用自然语言大致描述如下:

“有一个数据结构State。它是一个Monad。这是证明:...”

现在将其与问题中的extends 进行比较:

trait M[A] {
  def unit(a: A): M[A]
  def ★[B](f: A => M[B]): M[B]
}

case class State[A, S](run: S => (A, S)) extends M[A] { ... }

在这里,语句“某物是单子”应该被它自己试图描述的“某物”扩展。 M 既是“某物”有这样那样的方法的声明(unit*),但它也是被描述的东西。翻译成自然语言散文,这个公式大致对应于笨拙

M 声明它本身将被具有某些方法(unit* 等)的东西扩展,其定义指的是扩展的东西本身。”

这是一种特殊情况

M 声明它将被一些S 扩展,并且S 将满足一些引用S(因此也引用M)的属性”

这是一种特殊情况

M 声明 M 有一些属性”

这是创建各种自引用循环语句的已知方法,例如

M 声明 M 是假的。”

更广为人知的是说谎者的悖论

“这个说法是错误的。”

因此,F 有界多态性的出现和所有这些无效循环引用错误的爆发并非偶然:通过尝试将正在描述的事物与进行描述的语句合并,您正在创建这个原悖论的自指结构。

因此,我建议坚持标准表述,并将陈述与人们想要发表的陈述分开。


如何强制它编译

(以下所有内容仅用于教育目的,我不建议使用生成的代码;它仅用作演示几个有趣的编译错误的示例;长话短说:不要尝试使用F-bounded polymorphism 来定义 monad 接口,使用类型类代替)

extends M[A] 是一个危险信号。理论上,你可以让它工作,但它不会很漂亮。

这里有几个问题:

  1. 您绑定了两次S,因此State[A, S] 中的S 被来自unit[S] 的不可推断的S 遮蔽,因此没有任何东西可以组合在一起。这可以通过消除一些不必要的S-s 来解决。
  2. unit 中的A 似乎以一种意想不到的方式依赖于State[A, S] 绑定的类型。如果有自己的A就更好了。
  3. 即使您解决了这个问题,您也会立即遇到问题,即您的 M 方法中的返回类型过于模糊:您不想要以某种方式实现 M 的任何东西,您想要与实现M 的类型相同。这可以通过 F 有界多态性来解决,但这会导致更糟糕的问题...
  4. ...您需要 type-lambdas 来表达 F 有界多态性。如果没有 kind-projector 或一流的 lambda 支持,这会变得非常丑陋。
  5. 有很多不同寻常的东西:unit 通常称为pure,参数的顺序通常是State[S, A],因为第二个参数更适合类型推断。

将 Scala 3 的 lambda 类型支持与莫名其妙的无端不成比例的暴力相结合,我们因此获得:

trait M[F[X] <: M[F, X], A] {
  def unit[A](a: A): F[A]
  def ★[B](f: A => F[B]): F[B]
}

case class State[S, A](run: S => (A, S)) extends M[[X] =>> State[S, X], A] {
  def unit[A](a: A): State[S, A] = State((s: S) => (a, s))
  def ★[B](f: A => State[S, B]): State[S, B] = State((s0: S) => {
    val (a, s1) = this.run(s0)
    val (b, s2) = f(a).run(s1)
    (b, s2)
  })
}

...它可以编译,但使用起来很尴尬,因为它需要一个正确类型的 State 实例才能调用 unit,这违背了目的。

这是一个很好的练习,但生成的代码非常不理想。将 monad 与 extends 混合有很好的用例,但这不是其中之一。

【讨论】:

  • 感谢您这么长的回复。首先,是的,我可以在 unit[S] 和 ★[B, S] 中删除 S,这样可以避免这些错误,但我得到另一个方法运行不是 M[B] 成员的错误。如果我只是通过 State[S, B] 更改 M[B],那么我将不再从特征 M[A] 实现方法。所以 F-bounded polymorphism 似乎是解决方案,但是 M[F[X] <: m x a state>
  • @Shodz 我不明白你的最后一句话,你能试着改写一下吗?
  • 当然可以。我的意思是对上限的约束不允许使用像class BoxMonad[A](box: A) 这样的单一类型的类,所以我认为 M[F[A], A] 可以用来更通用。此外,在 scala 2 上,type-lambda M[({type λ[A]=State[S, A]})#λ, A] 不起作用,我得到一个循环引用,很好奇它在 scala 3 中起作用。关于你的第 5 点,我不知道 State[S , A] 我只是使用了相同顺序的元组 S => (A, S),它混合了我的想法。
  • @Shodz Re“上界的约束不允许使用具有单一类型的类,如类 BoxMonad[A](box: A)” - 它不仅允许这样做,它 强制你给它一个带有单一类型洞的类型构造器;这就是为什么我们必须将 State[S, A] 中的类型参数 S 与 type-lambda 绑定。
  • @Shodz "我得到一个循环引用" - 是的,发生这种情况是有充分理由的。通过尝试将 State 与 monad 定义绑定到单个对象中,您实际上是在为自己设置 this game。我在答案中添加了一个部分来解释为什么会发生这种情况。
猜你喜欢
  • 2017-08-03
  • 2015-04-30
  • 2015-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-11
  • 2010-12-13
  • 1970-01-01
相关资源
最近更新 更多