Проблема с выходом во вложенном рабочем процессе

Я пытаюсь написать свой собственный построитель Либо как часть моего стремления изучить вычислительные выражения в f #, но я столкнулся с тем, что, как мне кажется, является проблемой с методом Combine. Мой код до сих пор:

type Result<'a> = 
    | Failure
    | Success of 'a

type EitherBuilder() = 
    member this.Bind(m,f) = 
        match m with
        | Failure -> Failure
        | Success(x) -> f x
    member this.Yield x =
        Success(x)
    member this.YieldFrom x = 
        x
    member this.Combine(a,b) = 
        match a with 
        | Success(_) -> a
        | Failure -> b()
    member this.Delay y = 
        fun () -> y()
    member this.Run(func) = 
        func()

С помощью этого кода я тестирую Combine двумя тестами:

let either = new EitherBuilder()
...
testCase "returns success from 3 yields" <|
    fun _ -> 
        let value = either {
            yield! Failure 
            yield 4
            yield! Failure
            }
        value |> should equal (Success(4))
testCase "returns success with nested workflows" <|
    fun _ -> 
        let value = either {
            let! x = either { 
                yield! Failure 
                } 
            yield 5
            }
        value |> should equal (Success(5))

Первый тест проходит, как я и ожидал, но второй тест завершается со следующим сообщением:

Возникло исключение: «NUnit.Framework.AssertionException» в nunit.framework.dll либо проверяет/возвращает успех с вложенными рабочими процессами: Ошибка: Ожидается: <Success 5> Но было: <Failure>

Я не понимаю. x не выдается, так почему же это влияет на мой родительский рабочий процесс? Если я двигаться пусть! ниже выход тест проходит. Я смотрю на свою реализацию Combine, и мне кажется, что для пары Failure*Success фактический порядок аргументов не повлияет на результат, но все же кажется, что это так.


person DevNewb    schedule 13.11.2017    source источник


Ответы (1)


Предложения do! и let! в выражении обесцениваются до вызовов Bind. Это означает, что ваш Bind вызывается, когда вы делаете let! x = ....

В частности, ваш второй пример превращается в следующее:

let value = 
    either.Bind(
       either.YieldFrom Failure,   // yield! Failure
       fun x ->                    // let! x =
           either.Yield 5          // yield 5
    )

Таким образом, он даже не доходит до yield 5 — вычисление останавливается на let! x =.

Чтобы внутреннее вычисление «никогда не стало частью» внешнего, просто используйте let (без челки):

let value = either {
     let x = either { 
         yield! Failure 
         } 
     yield 5
     }

Это правильно вернет Success 5.

person Fyodor Soikin    schedule 13.11.2017
comment
Я полностью пропустил это следствие let!, спасибо :) - person DevNewb; 14.11.2017