使用多态 ADT 类型检查的错误 Scala 代码
Erroneous Scala code using polymorphic ADT type checks
我正在做一些练习以更好地理解 IO monad(在 Functional Programming in Scala 之后),并设法编写了一些错误的代码,这些代码以某种方式通过了编译,这让我有些头疼。
在下面的示例中,我正在编写一个 IO monad 的堆栈安全 解释器。该代码位于多态代数数据类型 (FlatMap[A, B]
) 的模式匹配中。代码中的错误是k1 andThen k2
;这两个函数无法组合,因为 k1
返回的类型 (IO[B]
) 与 k2
期望的类型 (B
) 不同。 代码仍然以某种方式进行类型检查,这显然是一个类型检查错误,因为在运行时,自动拆箱时有一个ClassCastException
(就像我在使用Java 中的不安全转换)。也没有发出编译器警告。
代码(也在 gist 上找到):
object IOMonadExercise extends App {
sealed trait IO[A]
case class Return[A](value: A) extends IO[A]
case class Suspend[A](f: () => A) extends IO[A]
case class FlatMap[A, B](io: IO[A], cont: A => IO[B]) extends IO[B]
object IO {
def apply[A](a: => A): IO[A] = Suspend(() => a)
}
object Interpreter {
def run[A](io: IO[A]): A = {
io match {
case Return(a) => a
case Suspend(f) => f()
case FlatMap(Return(a), cont) => run(cont(a))
case FlatMap(Suspend(f), cont) => run(cont(f()))
// this case compiles for whatever reason but shouldn't type check (k1 returns IO[B] and k2 expects just B)
// accordingly, there is a ClassCastException in the runtime
case FlatMap(FlatMap(io1, k1), k2) => run(FlatMap(io1, k1 andThen k2))
// this case is the one that actually works
// case FlatMap(FlatMap(io1, k1), k2) => run(flatten(io1, k1, k2))
}
}
def flatten[A, B, C](io: IO[A], k1: A => IO[B], k2: B => IO[C]): FlatMap[A, C] = {
FlatMap(io, a => FlatMap(k1(a), k2))
}
}
def sum(i: Int): IO[Int] = {
Stream.range(0, i).foldLeft(IO(0))((io, i) => FlatMap(io, (s: Int) => IO(s + i)))
}
val n = 100000
val sumNIO: IO[Int] = sum(n)
val sumN: Int = Interpreter.run(sumNIO)
println(s"sum of 1..$n by IO loop : $sumN")
println(s"sum of 1..$n by math expr: ${n * (n - 1) / 2}")
assert(sumN == n * (n - 1) / 2)
}
这是怎么回事?这是编译器错误吗?或者这是类型推断的已知限制?或者对此有解释吗?
我已经在 Scala 2.11.8 和 2.12.0 上进行了测试,行为似乎是一样的:代码编译时没有警告。
我认为这是 SI-5195 错误的一个例子。如果你手动构造嵌套的FlatMap
,你不能写andThen
,因为所有类型都是已知的,k1
和k2
显然是不可组合的。
但是io1
、k1
和k2
的模式匹配类型是事先不知道的,它们必须被推断出来,正如我们所看到的那样,它们是错误的推断。 [...]
编辑
这是另一个解释它如何进行类型检查的尝试:如果你开始自己推断 k1
和 k2
的类型,你会想出
k1: X => IO[Y]
和 k2: Y => IO[A]
对于一些 X
和 Y
- 加上
k1 andThen k2
你需要 IO[Y] <: Y
那么是否存在满足这些限制的类型Y
?是的,它是 Any
。
但是当你应用它时,IO[Y]
变成 Suspend[Int]
而 Y
只是 Int
子类型关系不成立。
我正在做一些练习以更好地理解 IO monad(在 Functional Programming in Scala 之后),并设法编写了一些错误的代码,这些代码以某种方式通过了编译,这让我有些头疼。
在下面的示例中,我正在编写一个 IO monad 的堆栈安全 解释器。该代码位于多态代数数据类型 (FlatMap[A, B]
) 的模式匹配中。代码中的错误是k1 andThen k2
;这两个函数无法组合,因为 k1
返回的类型 (IO[B]
) 与 k2
期望的类型 (B
) 不同。 代码仍然以某种方式进行类型检查,这显然是一个类型检查错误,因为在运行时,自动拆箱时有一个ClassCastException
(就像我在使用Java 中的不安全转换)。也没有发出编译器警告。
代码(也在 gist 上找到):
object IOMonadExercise extends App {
sealed trait IO[A]
case class Return[A](value: A) extends IO[A]
case class Suspend[A](f: () => A) extends IO[A]
case class FlatMap[A, B](io: IO[A], cont: A => IO[B]) extends IO[B]
object IO {
def apply[A](a: => A): IO[A] = Suspend(() => a)
}
object Interpreter {
def run[A](io: IO[A]): A = {
io match {
case Return(a) => a
case Suspend(f) => f()
case FlatMap(Return(a), cont) => run(cont(a))
case FlatMap(Suspend(f), cont) => run(cont(f()))
// this case compiles for whatever reason but shouldn't type check (k1 returns IO[B] and k2 expects just B)
// accordingly, there is a ClassCastException in the runtime
case FlatMap(FlatMap(io1, k1), k2) => run(FlatMap(io1, k1 andThen k2))
// this case is the one that actually works
// case FlatMap(FlatMap(io1, k1), k2) => run(flatten(io1, k1, k2))
}
}
def flatten[A, B, C](io: IO[A], k1: A => IO[B], k2: B => IO[C]): FlatMap[A, C] = {
FlatMap(io, a => FlatMap(k1(a), k2))
}
}
def sum(i: Int): IO[Int] = {
Stream.range(0, i).foldLeft(IO(0))((io, i) => FlatMap(io, (s: Int) => IO(s + i)))
}
val n = 100000
val sumNIO: IO[Int] = sum(n)
val sumN: Int = Interpreter.run(sumNIO)
println(s"sum of 1..$n by IO loop : $sumN")
println(s"sum of 1..$n by math expr: ${n * (n - 1) / 2}")
assert(sumN == n * (n - 1) / 2)
}
这是怎么回事?这是编译器错误吗?或者这是类型推断的已知限制?或者对此有解释吗?
我已经在 Scala 2.11.8 和 2.12.0 上进行了测试,行为似乎是一样的:代码编译时没有警告。
我认为这是 SI-5195 错误的一个例子。如果你手动构造嵌套的FlatMap
,你不能写andThen
,因为所有类型都是已知的,k1
和k2
显然是不可组合的。
但是io1
、k1
和k2
的模式匹配类型是事先不知道的,它们必须被推断出来,正如我们所看到的那样,它们是错误的推断。 [...]
编辑
这是另一个解释它如何进行类型检查的尝试:如果你开始自己推断 k1
和 k2
的类型,你会想出
k1: X => IO[Y]
和k2: Y => IO[A]
对于一些X
和Y
- 加上
k1 andThen k2
你需要IO[Y] <: Y
那么是否存在满足这些限制的类型Y
?是的,它是 Any
。
但是当你应用它时,IO[Y]
变成 Suspend[Int]
而 Y
只是 Int
子类型关系不成立。