2015-04-15 2 views
8

Я раньше думал, что часть цели реализации заключается в том, чтобы избежать этой самой проблемы, так что, может быть, я делаю что-то явно немое?Как избежать переполнения стека при использовании бесплатной монады Скалаза?

Вот код:

// Stack overflow 
import scalaz._ 

sealed trait Command[T] 
case class Wait(ms: Long) extends Command[Unit] 

case object Evaluator extends (Command ~> Id.Id) { 
    override def apply[T](cmd: Command[T]) = cmd match { 
    case Wait(t) => Thread.sleep(t) 
    } 
} 

object Api { 
    def sleep(ms: Long): Free.FreeC[Command, Unit] = Free.liftFC(Wait(ms)) 
} 

val sleep: Free.FreeC[Command, Unit] = 
    Api.sleep(1).flatMap { _ => sleep } 

Free.runFC(sleep)(Evaluator) 

Примечания: Я понимаю, что это глупо :) На практике, моя команда класс имеет много команд, и у меня есть команда, которая делает этот же цикл ... в основном, опросить какое-то состояние, если true abort, если false, продолжать ждать.

Я хочу избежать переполнения стека, что это вызывает ... Я ДУМАЮ, что это уже батут, но я думаю, мне нужно вручную сделать это снова? Есть ли чистый способ сделать это в рамках свободного монадского мышления?

Update:

Думая далее на этом, я думаю, что проблема не сон Free Монада, а скорее Id.Id монада TAHT мы связываем в основе оценки ... так что я пытался что-то вроде:

case object Evaluator2 extends (Command ~> ({ type t[x] = Free[Id.Id, x] })#t) { 
    override def apply[T](cmd: Command[T]) = cmd match { 
    case Wait(t) => Thread.sleep(t); Free.liftF[Id.Id, Unit](()) 
    } 
} 

Free.runFC[Command, ({ type t[x] = Free[Id.Id, x] })#t, Unit](sleep)(Evaluator2)(Free.freeMonad[Id.Id]) 

Но проблема в том, что он будет оценивать только один шаг. В идеале я бы хотел, чтобы runFC блокировался до тех пор, пока не будет выполнено какое-либо условие (или, в этом случае, цикл навсегда, пока я не убью его, но без переполнения стека)

ответ

6

Монастырь Id не батут. Вы оказываетесь в бесконечной взаимной рекурсии между методом bind для монады Id и методом foldMap для свободной монады. Используйте Trampoline или Task вместо Id.

2

С @ ответ Apocalisp, есть было добавление BindRec и foldMapRec класса типов методов, которые могут быть использованы для оценки стека безопасно непосредственно Id (или любой другой «хвост-рекурсивных» монады). Подробнее см. Stack Safety for Free.