проблема с вашей реализацией в том, что она возвращает «unit -> 'a» для каждого вызова Bind, поэтому вы получите другой тип результата для разного количества шагов (в общем, это подозрительное определение монада / вычислительное выражение).
Правильным решением должно быть использование некоторого другого типа, который может представлять вычисление с произвольным числом шагов. Вам также нужно будет различать два типа шагов - некоторые шаги просто оценивают следующий шаг вычисления, а некоторые возвращают результат (через ключевое слово return
). Я буду использовать тип seq<option<'a>>
. Это ленивая последовательность, поэтому чтение следующего элемента оценит следующий шаг вычисления. Последовательность будет содержать None
значений, за исключением последнего значения, которое будет Some(value)
, представляющего результат, возвращаемый с использованием return
.
Еще одна подозрительная вещь в вашей реализации - нестандартный тип члена Bind
. Тот факт, что ваша привязка принимает значение в качестве первого параметра, означает, что ваш код выглядит немного проще (вы можете написать let! a = 1
), однако вы не можете составлять пошаговые вычисления. Вы можете захотеть написать:
let foo() = stepwise {
return 1; }
let bar() = stepwise {
let! a = foo()
return a + 10 }
Тип, который я описал выше, также позволит вам написать это. Когда у вас есть тип, вам нужно просто следовать подписи типа Bind
и Return
в реализации, и вы получите следующее:
type Stepwise() =
member x.Bind(v:seq<option<_>>, rest:(_ -> seq<option<_>>)) = seq {
let en = v.GetEnumerator()
let nextVal() =
if en.MoveNext() then en.Current
else failwith "Unexpected end!"
let last = ref (nextVal())
while Option.isNone !last do
// yield None for each step of the source 'stepwise' computation
yield None
last := next()
// yield one more None for this step
yield None
// run the rest of the computation
yield! rest (Option.get !last) }
member x.Return v = seq {
// single-step computation that yields the result
yield Some(v) }
let stepwise = Stepwise()
// simple function for creating single-step computations
let one v = stepwise.Return(v)
Теперь давайте рассмотрим использование типа:
let oneStep = stepwise {
// NOTE: we need to explicitly create single-step
// computations when we call the let! binder
let! y = one( "foo" )
printfn "got: %A" y
return y + "bar" }
let threeSteps = stepwise {
let! x = oneStep // compose computations :-)
printfn "got: %A" x
let! y = one( x + "third" )
printfn "got: %A" y
return "returning " + y }
Если вы хотите выполнить вычисления шаг за шагом, вы можете просто перебрать возвращаемую последовательность, например, используя ключевое слово F # for
. Далее также печатается индекс шага:
for step, idx in Seq.zip threeSteps [ 1 .. 10] do
printf "STEP %d: " idx
match step with
| None _ -> ()
| Some(v) -> printfn "Final result: %s" v
Надеюсь, это поможет!
PS: я нашел эту проблему очень интересной! Не возражаете, если я добавлю свой ответ в сообщение для своего блога (http://tomasp.net/blog)? Спасибо!