我正在做一些练习,以更好地理解IO单子(以下Functional Programming in Scala),并设法编写一些错误的代码,以某种方式通过编译,并引起我一些头痛。使用多态ADT类型检查的错误斯卡拉代码
在下面的例子中,我写一个IO单子一叠安全解释。代码在多态代数数据类型(FlatMap[A, B]
)中进行模式匹配。代码中的错误是k1 andThen k2
;这两个函数不能组成,因为正在返回与k2
预期的不同类型(IO[B]
)(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,行为似乎是相同的:代码编译时没有警告。
看起来确实如此!经过对这个方向的一些研究后,我也发现了这个:http://stackoverflow.com/questions/20359696/scala-pattern-match-infers-any-instead-of-an-existential-type-breaks-type- saf 而且这个问题似乎与SI-5195有关:https://issues.scala-lang.org/browse/SI-6680 – dzs