Как избежать переполнения стека при использовании бесплатной монады scalaz?

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

Вот код:

    // 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)

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

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

Обновлять:

Размышляя об этом дальше, я думаю, что проблема не в Sleep Free Monad, а скорее в монаде Id.Id, с которой мы связываемся при оценке... поэтому я попробовал что-то вроде:

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 блокировался до тех пор, пока не будет выполнено какое-то условие (или, в этом случае, зацикливаться до тех пор, пока я не убью его, но без переполнения стека)


person A Question Asker    schedule 15.04.2015    source источник


Ответы (2)


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

person Apocalisp    schedule 15.04.2015

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

person Tomas Mikula    schedule 13.12.2016