4

我正在做一些练习,以更好地理解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,行为似乎是相同的:代码编译时没有警告。

回答

1

我认为这是一个SI-5195错误的情况。如果手动构建嵌套的FlatMap,则不能写入andThen,因为所有类型都是已知的,并且​​和k2显然不可组合。

但在这种模式匹配类型的io1,​​和k2是事先不知道,他们必须推断,当我们看到他们推断是错误的。 [...]

编辑 这里是另一个试图解释它如何类型检查:如果你开始推断类型​​和k2自己,你就会想出

  • k1: X => IO[Y]k2: Y => IO[A]k1 andThen k2一些XY
  • 再加上你需要IO[Y] <: Y

那么是否存在满足这些限制的任何类型Y?是的,这是Any。 但是,当您应用它时,IO[Y]变为Suspend[Int]Y只是Int,子类型关系不成立。

+1

看起来确实如此!经过对这个方向的一些研究后,我也发现了这个: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