问题
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