Trampolining scalaz' Monad.whileM_ to prevent stack overflow

假装没事ソ 提交于 2019-12-24 11:25:58

问题


I'm using scalaz' Monad.whileM_ to implement a while loop in a functional way as follows:

object Main {

  import scalaz._
  import Scalaz._

  import scala.language.higherKinds

  case class IState(s: Int)

  type IStateT[A] = StateT[Id, IState, A]
  type MTransT[S[_], A] = EitherT[S, String, A]
  type MTrans[A] = MTransT[IStateT, A]

  def eval(k: Int): MTrans[Int] = {
    for {
      state <- get[IState].liftM[MTransT]
      _ <- put(state.copy(s = (state.s + 1) % k)).liftM[MTransT]
    } yield (k + 1)
  }

  def evalCond(): MTrans[Boolean] = {
    for {
      state <- get[IState].liftM[MTransT]
    } yield (state.s != 0)
  }


  def run() = {
    val k = 10
    eval(k).whileM_(evalCond()).run(IState(1))
  }
}

While this works for small k, it results in a StackOverflow error for large k (e.g. 1000000). Is there a way to trampoline whileM_ or is there a better way to be stack safe?


回答1:


Use scalaz.Free.Trampoline instead of scalaz.Id.Id.

type IStateT[A] = StateT[Trampoline, IState, A]

The state operations used here return State[S, A] which is just an alias for StateT[Id, S, A]. You need to use the lift[M[_]] function defined on StateT to lift StateT[Id, S, A] to StateT[Trampoline, S, A].

def eval(k: Int): MTrans[Int] = {
  for {
    state <- get[IState].lift[Trampoline].liftM[MTransT]
    _ <- put(state.copy(s = (state.s + 1) % k)).lift[Trampoline].liftM[MTransT]
  } yield (k + 1)
}

def evalCond(): MTrans[Boolean] = {
  for {
    state <- get[IState].lift[Trampoline].liftM[MTransT]
  } yield (state.s != 0)
}

Finally, calling .run(IState(1)) now results in Trampoline[(IState, String \/ Unit)]. You must additionally run this as well.

eval(k).whileM_(evalCond()).run(IState(1)).run


来源:https://stackoverflow.com/questions/30652980/trampolining-scalaz-monad-whilem-to-prevent-stack-overflow

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!