Incorrect Scala code using ADT-type polymorphic checks

I am doing some exercises to better understand the IO monads (after Functional programming in Scala ) and I managed to write some erroneous code that somehow passed the compilation and caused me a headache.

In the example below, I am writing a stack security interpreter in the IO monad. The code is in pattern matching in the polymorphic Algebraic data type ( FlatMap[A, B]). Error in the code k1 andThen k2; two functions cannot constitute, because it k1returns a different type ( IO[B]) than expects k2( B). The code is still type-checks somehow , and this is obviously a type-checking error, because at runtime it is ClassCastExceptionduring automatic unpacking (exactly the same as if I used unsafe translation in Java). There are also no compiler warnings.

Code (also found on 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)
}

What's happening? Is this a compiler error? Or is this a known type inference restriction? Or is there an explanation for this?

Scala 2.11.8, 2.12.0, , , : .

+4
1

, 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, .

+1

Source: https://habr.com/ru/post/1660471/


All Articles