compiler error message when using State monad for memoization

68 views Asked by At

I have a problem to make a working version of the Euler project problem 31 with the use of State trait (inspired from scalaz)

First, I have a solution with a mutable HashMap for memoization. It works but i would like to use the State monad, to understand it and to improve my skills.

I have used it with the fibonacci example, but when i attempt to apply the same technique to my case, i have a compiler error that i don't understand.

I use this implementation for State :

trait State[S, A] {
  val run: S => (S, A)
  def apply(s: S): (S, A) = run(s)
  def eval(s: S): A = run(s)._2

  def map[B](f: A => B): State[S, B] =
    State { s: S =>
      val (s1, a) = run(s)
      (s1, f(a))
    }

  def flatMap[B](f: A => State[S, B]): State[S, B] =
    State { s: S =>
      val (s1, a) = run(s)
      f(a)(s1)
    }

}

object State {
  def apply[S, A](f: S => (S, A)): State[S, A] = new State[S, A] {
    final val run = f
  }

  def init[S, A](a: A) = State { s: S => (s, a) }
  def update[S, A](f: S => S): State[S, Unit] = State { s: S => (f(s), ()) }
  def gets[S, A](f: S => A): State[S, A] = State { s: S => (s, f(s)) }

}

my attempt to use it is here :

val coins = List(1, 2, 5, 10, 20, 50, 100, 200)
type MemoKey = (List[Int], Int)
type MemoType = Map[MemoKey, Int]

def ways(listCoins: List[Int], amount: Int): Int = {

  def ways_impl(coins: List[Int], sum: Int): State[MemoType, Int] = (coins, sum) match {
    case (Nil, 0) => State.init(1)
    case (Nil, _) => State.init(0)
    case (c :: cs, _) =>
      for {
        memoed <- State.gets { m: MemoType => m.get((coins, sum)) }
        res <- memoed match {
          case Some(way) => State.init[MemoType, Int](way)
          case None =>
            (for {
              i <- 0 to sum / c
              r <- ways_impl(cs, sum - i * c)
              _ <- State.update { m: MemoType => m + ((coins, sum) -> r) }
            } yield r).sum
        }
      } yield res
  }
  ways_impl(listCoins, amount) eval (Map())

I have a compiler error at this line :

              r <- ways_impl(cs, sum - i * c)

The compiler said :

type mismatch; found : State[MemoType,Int] (which expands to) State[scala.collection.immutable.Map[(List[Int], Int),Int],Int] required: scala.collection.GenTraversableOnce[?]

For information, here is my first version with mutable map :

import scala.collection.mutable._
val memo = HashMap[(List[Int], Int), Int]()

val coins = List(1, 2, 5, 10, 20, 50, 100, 200)

def memoWays(coins: List[Int], sum: Int): Int = {
  memo.getOrElse((coins, sum), {
    val y = ways(coins, sum)
    memo += ((coins, sum) -> y)
    y
  })
}

// brute force method with memoization
def ways(coins: List[Int], sum: Int): Int = (coins, sum) match {
  case (Nil, 0) => 1
  case (Nil, _) => 0
  case (c :: cs, n) =>
    (for {
      i <- 0 to n / c
      r = memoWays(cs, n - i * c)
    } yield r).sum
}

println(s"result=${Mesure(ways(coins, 200))}")

What does that error mean ? Why the compiler want a GenTraversableOnce instead of State ? What kind of thing i don't understand on State monad ?

And, if i may, I have an optional question : Is my way to memoize with State Monad, is a good choice, or my first implementation with mutable map is better anyway ?

1

There are 1 answers

1
acjay On BEST ANSWER

The problem is that your for comprehension is attempting to flatMap two unrelated types: a Range and a State. You're going to have to refactor, although off the top of my head, it's not clear to me how you'll be able to leverage State in a simple way. I'd probably use an immutable Map for the memo, a List to represent the future iterations to be tried, and simple recursion to iterate.