2017-01-15 19 views
0

у меня есть цепь вычислений, которая изменяет состояние контекста:scalaz перерыв управления Free Монады и пауза для цепочки вычислений

case class Context(...) 
type Step = (Context => Context) 
val step1: Step = ctx => { ctx.copy(...) } 
val step2: Step 
val step3: Step 
// ... 
val stepN: Step 
val chain = List(step1, step2, step3, ..., stepN).toStream 

И я хотел бы ввести возвращаемое значение вычисления для управления потоком из этой цепи:

trait Cont 
case class Break[Context](ctx: Context) extends Cont 
case class Pause[Context](ctx: Context) extends Cont 
case class Continue[Context](ctx: Context) extends Cont 

Перерыв - означает отмену цепи после выполнения текущего шага,

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

Продолжить - означает нормальное продолжение потока.

Каждый шаг должен возвращать текущее значение контекста, заключенного в Cont.

В настоящее время я делаю это, разворачивая поток шагов (цепочка val: поток [Cont [Step]]) и проверяя chain.isEmpty и chain.head, чтобы проверить, осталось ли какое-то вычисление.

Как это сделать, используя Free monads в scalaz?

ответ

1

Вы можете достичь этого, используя монаду продолжения scalaz, scalaz.Cont.

К сожалению, документации нет и нет примеров использования scalaz.Cont. Таким образом, чтобы получить общее представление о продолжениях и что они для, посмотрите на

Суть использования продолжений в том, что мы не будем возвращать значения из наших расчетов, но передать управление продолжением (часто используется переменная k) и продолжение определяет, что произойдет дальше.

При этом, давайте посмотрим, как это будет выглядеть в вашем конкретном примере:

  • сначала несколько определений типа; обратите внимание, что Continuation, «Следующий шаг», это функция, которая принимает Context и возвращает что-то типа Return

    case class Context(i: Int) 
    type Continuation = Context => Return 
    
  • затем определить некоторые типы для управления потоком внутри нашей вычислительной сети; обратите внимание, что Pause также возвращает Continuation, так как мы хотим, чтобы иметь возможность продолжить вычисление позже

    trait Return 
    case class Break(context: Context) extends Return 
    case class Pause(context: Context, continuation: Continuation) extends Return 
    case class Done(context: Context) extends Return 
    
  • Затем мы определяем наши шаги, как классы для того, чтобы иметь возможность использовать их свободной монады ...

    trait Step[A] 
    case class Step1(c: Context) extends Step[Context] 
    case class Step2(c: Context) extends Step[Context] 
    case class Step3(c: Context) extends Step[Context] 
    
  • ... и поднять их в контексте свободной монады

    type Command[A] = Free[Step, A] 
    protected implicit def liftStep[A](step: Step[A]): Command[A] = 
        Free.liftF(step) 
    
    def step1(c: Context): Command[Context] = Free.liftF(Step1(c)) 
    def step2(c: Context): Command[Context] = Free.liftF(Step2(c)) 
    def step3(c: Context): Command[Context] = Free.liftF(Step3(c)) 
    
  • прямо сейчас мы строим нашу цепочку вычислений; обратите внимание, что мы передаем результат предыдущего вычисления (например, c1) в следующий (например, step2(c1)); Если это должно произойти более неявно, может быть, состояние монада следует рассматривать для передачи состояния от продолжения к продолжению

    val script = for { 
        c0 <- Free.point(Context(0)) 
        c1 <- step1(c0) 
        c2 <- step2(c1) 
        c3 <- step3(c2) 
    } yield c3 
    
  • и определить интерпретатор (т.е. step), который оценивает отдельные этапы; увидеть комментарии для объяснения того, как отдельные шаги могут контролировать поток вычислений

    type Process[A] = Cont[Return, A] 
    val step: Step ~> Process = new (Step ~> Process) { 
        override def apply[A](action: Step[A]): Process[A] = action match { 
        case Step1(c) => 
         Cont { k: Continuation => 
         // call next computation with context value set to 1 
         k(c.copy(i = 1)) 
         } 
        case Step2(c) => 
         Cont { k: Continuation => 
         // pause computation ; to be able to resume, continuation k is returned 
         Pause(c.copy(i = 2), k) 
         } 
        case Step3(c) => 
         Cont { k: Continuation => 
         // after the pause, the computation is resumed here 
         k(c.copy(i = 3)) 
         // alternatively: break computation (i.e. not resumable) 
         //Break(c.copy(i = 3)) 
         } 
        } 
    } 
    
  • запустить script с переводчиком step, чтобы получить на продолжение

    val process: Cont[Return, Context] = script.foldMap(step) 
    
  • запустить продолжение process; Отметим, что метод запуска ожидает Continuation, который будет продолжением последнего шага вычислений (т.е. c будет Context(3) от Step3)

    var result = process.run(c => Done(c)) 
    
  • наконец, написать код, чтобы выполнить полную цепочку и продолжить, если вычисление просто приостановлено или остановить, если Break или Done является результатом

    var continue = true 
    do { 
        continue = result match { 
        case Pause(c, continuation) => 
         println(s"Pause: $c") 
         result = continuation(c) 
         true 
        case Break(c) => 
         println(s"Break: $c") 
        false 
        case Done(c) => 
         println(s"Done: $c") 
        false 
        } 
    } while(continue) 
    

результат:

Pause: Context(2) 
Done: Context(3) 
+0

большой подробный ответ, спасибо! – malikbakt