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

Я пытаюсь написать свой собственный конструктор Either как часть моего квеста по изучению выражений вычислений в 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 пара фактический порядок аргументов не повлияет на результат, но все же кажется, что это делает

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,

Другие вопросы по тегам