Scala compiler does not infer type properly

I am reading Chapter 6 of Functional Programming in Scala . I've approached the last exercise of the Chapter (ie: 6.11) and it makes me doubtful about some line of codes.

Here it is the description of the exercise:

Let's implement a finite state automaton that models a simple candy dispenser. The machine has two inputs: you can insert a coin, or you can dispense candy by turning the knob. It can be in one of two states: locked or unlocked. It also tracks how many candies are left and how many coins it contains.

  • Inserting a coin into a locked machine will unlock it if there's still any candy left.
  • Turning the knob on an unlocked machine will cause it to dispense one candy and return to a locked state.
  • Turning the knob on a locked machine or inserting a coin into an unlocked machine has no effect.
  • A machine that's out of candy ignores any kind of input.
  • The method simulateMachine should operate the machine based on a list of inputs and return the number of coins and candies left in the machine. For instance, if the input Machine has 10 coins and 5 candies, and a total of 4 candies are bought successfully, the output should be (14, 1).

    The following is a testable unit of my own implementation.

    object Test extends App {
    
      class Comparison(value: Int) {
        def shouldBe(other: Int): Boolean = value == other
      }
    
      implicit def enrichIntegers(value: Int) = new Comparison(value)
    
      val machine = Machine(locked  = true, candies = 5, coins = 10)
      val input = List(Coin, Turn, Coin, Turn, Coin, Turn, Coin, Turn)
      val result = StateChange.simulateMachine(input)
      val tuple: ((Int, Int), Machine) = result.run(machine)
      val ((coins, candies), _) = tuple
    
      (coins shouldBe 14) && (candies shouldBe 1) match {
        case true => println("Ok")
        case false => println("Ko")
      }
    }
    
    sealed trait Input
    case object Coin extends Input
    case object Turn extends Input
    case class Machine(locked: Boolean, candies: Int, coins: Int)
    
    case class State[S,+A](run: S => (A, S)) {
      def map[B](f: A => B): State[S, B] = State(
        s => {
          val (a, newState) = this.run(s)
          (f(a), newState)
        }
      )
      def map2[B,C](sb: State[S, B])(f: (A, B) => C): State[S, C] = State(
        s => {
          val (a, newState) = this.run(s)
          val (b, finalState) = sb.run(newState)
          (f(a, b), finalState)
        }
      )
    
      def flatMap[B](f: A => State[S, B]): State[S, B] = State(
        s => {
          val (a, state): (A, S) = this.run(s)
          f(a).run(state)
        }
      )
    }
    
    object State {
    
      def get[S]: State[S, S] = State(s => (s, s))
      def set[S](s: S): State[S, Unit] = State(_ => ((), s))
    
      def modify[S](f: S => S): State[S, Unit] = {
        for {
          s <- get
          _ <- set(f(s))
        } yield ()
      }
    
      def unit[S, A](a: A): State[S, A] = State(s => (a,s))
    
      def sequence[S, A](fs: List[State[S,A]]): State[S, List[A]] = {
        fs.foldRight[State[S, List[A]]](State.unit(Nil))((elem, acc) => elem.map2(acc)((a,b)=>a::b))
      }
    }
    
    object StateChange {
      def update(i: Input)(s: Machine) = {
        (i,s) match {
          case (_, Machine(_,0,_)) => s
          case (Coin, Machine(false,_,_)) => s
          case (Turn, Machine(true,_,_)) => s
          case (Coin, Machine(true,candies,coins)) => Machine(false,candies,coins+1)
          case (Turn, Machine(false,candies,coins)) => Machine(true,candies-1,coins)
        }
      }
    
      def simulateMachine(inputs: List[Input]): State[Machine, (Int, Int)] = {
        import State._
        val list: State[Machine, List[Unit]] = sequence(inputs.map(input => modify(update(input))))
        list.flatMap((_: List[Unit]) =>
           State(
             s => {
               val currentState: State[Machine, Machine] = get   // <= my doubt is here
               val (currentMachine, newState) = currentState.run(s)
               ((currentMachine.coins, currentMachine.candies), newState)
             }
           )
         )
      }
    }
    

    My doubt is about the following line in simulateMachine definition:

    val getState: State[Machine, Machine] = get
    

    simulateMachine implements a desugared version I wrote to better understanding the internals of the original for comprehension I actually wrote to solve the exercise.

    My question is:

    why I need to explicitly specify the type of currentState to make the code compiling? Indeed, without that State[Machine, Machine] type definition the above snippet does not compile.

    Why the compiler is not able to infer the actual type? Is that just a matter of type inference limitation or am I missing something here?

    Hope my question is clear, thank you!


    If you don't supply type arguments to get and don't specify the type of currentState explicitly Scala has no way of knowing the type. It would have to infer the type backwards from ((currentMachine.coins, currentMachine.candies), newState) and val (currentMachine, newState) = currentState.run(s) but Scala's type inference is not powerful enough to do that.

    链接地址: http://www.djcxy.com/p/66214.html

    上一篇: 在Vim中自动完成

    下一篇: Scala编译器不能正确推断类型