这个例子可能会被强制编译(参见第 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] 是一个危险信号。理论上,你可以让它工作,但它不会很漂亮。
这里有几个问题:
- 您绑定了两次
S,因此State[A, S] 中的S 被来自unit[S] 的不可推断的S 遮蔽,因此没有任何东西可以组合在一起。这可以通过消除一些不必要的S-s 来解决。
-
unit 中的A 似乎以一种意想不到的方式依赖于State[A, S] 绑定的类型。如果有自己的A就更好了。
- 即使您解决了这个问题,您也会立即遇到问题,即您的
M 方法中的返回类型过于模糊:您不想要以某种方式实现 M 的任何东西,您想要与实现M 的类型相同。这可以通过 F 有界多态性来解决,但这会导致更糟糕的问题...
- ...您需要 type-lambdas 来表达 F 有界多态性。如果没有 kind-projector 或一流的 lambda 支持,这会变得非常丑陋。
- 有很多不同寻常的东西:
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 混合有很好的用例,但这不是其中之一。