Какова роль `while`-циклов в выражениях вычислений в F #?

Если вы определяете метод While объекта-строителя, вы можете использовать While -loops в выражения вычислений. Подпись метода While:

member b.While (predicate:unit->bool, body:M<'a>) : M<'a>

Для сравнения, сигнатура метода For:

member b.For (items:seq<'a>, body:unit->M<'a>) : M<'a>

Вы должны заметить, что в While -методе тело является простым типом, а не функцией, как в методе For.

Вы можете вставлять некоторые другие выражения, такие как let и вызовы функций внутри ваших вычислений, но это невозможно выполнить в While -loop более одного раза.

builder {
    while foo() do
      printfn "step"
      yield bar()
}

Почему While -loop не выполняется более одного раза, а просто повторяется? Почему существенное отличие от for-loops? Еще лучше, есть ли какая-то предполагаемая стратегия использования while-loops в выражениях вычислений?

Ответ 1

Если вы посмотрите как вычисляются выражения вычислений, вы увидите, что

while foo() do
  printfn "step"
  yield bar()

переводится на что-то вроде

builder.While(fun () -> foo(), 
              builder.Delay(fun () -> 
                              printfn "step"
                              builder.Yield(bar()))))

Этот перевод позволяет обрабатывать тело цикла while несколько раз. Хотя ваши сигнатуры типов точны для некоторых выражений вычисления (например, seq или async), обратите внимание, что вставка вызова в Delay может привести к другой сигнатуре. Например, вы можете определить построитель списка следующим образом:

type ListBuilder() =
  member x.Delay f = f
  member x.While(f, l) = if f() then l() @ (x.While(f, l)) else []
  member x.Yield(i) = [i]
  member x.Combine(l1,l2) = l1 @ l2()
  member x.Zero() = []
  member x.Run f = f()

let list = ListBuilder()

Теперь вы можете оценить выражение типа:

list {
  let x = ref 0
  while !x < 10 do
    yield !x
    x := !x + 1
}

чтобы получить эквивалент [0 .. 9].

Здесь наш метод While имеет подпись (unit -> bool) * (unit -> 'a list) -> 'a list, а не (unit -> bool) * 'a list -> 'a list. В общем случае, когда операция Delay имеет тип (unit -> M<'a>) -> D<M<'a>>, сигнатура метода While будет (unit -> bool) * D<M<'a>> -> M<'a>.