在计算表达式中纠缠TryWith

时间:2014-02-15 17:36:45

标签: f# computation-expression

(未能'grok'FParsec,我按照我在某处读到的建议开始尝试自己编写一个小解析器。不知怎的,我发现了什么看起来像是一个机会尝试和monadify它,现在我有N个问题......)

这是我的'结果'类型(简化)

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

这是计算表达式构建器

type ResultBuilder() =
    member m.Return a = Success(a)
    member m.Bind(r,fn) =
        match r with
        | Success(a) -> fn a
        | Failure(m) -> Failure(m)

在第一个例子中,一切都按预期工作(编译):

module Parser = 
    let res = ResultBuilder()

    let Combine p1 p2 fn = 
        fun a -> res { let! x = p1 a
                       let! y = p2 a
                       return fn(x,y) }

我的问题在于:我希望能够捕捉到'组合'功能中的任何失败并返回失败,但它说我应该定义'零'。

    let Combine2 p1 p2 fn =
        fun a -> res { let! x = p1 a
                       let! y = p2 a
                       try
                          return fn(x,y) 
                       with
                         | ex -> Failure(ex.Message) }

我不知道应该在Zero中返回什么,我只是扔了member m.Zero() = Failure("hello world"),现在它说我需要TryWith

所以:

member m.TryWith(r,fn) =
    try 
        r()
    with
     | ex -> fn ex

现在它想要延迟,所以member m.Delay f = (fun () -> f())

它说(在ex -> Failure),This expression should have type 'unit', but has type 'Result<'a>',然后我举起手臂转向你们......

播放链接:http://dotnetfiddle.net/Ho1sGS

2 个答案:

答案 0 :(得分:2)

with块也应该返回计算表达式的结果。由于您要返回Result.Failure,您需要定义成员m.ReturnFrom a = a并使用它从with块返回失败。在try块中,您还应指定fn如果不抛出则返回Success。

let Combine2 p1 p2 fn =
            fun a -> res { let! x = p1 a
                           let! y = p2 a
                           return! 
                                try
                                    Success(fn(x,y))
                                with
                                    | ex -> Failure(ex.Message)
                         }

<强>更新

原始实现显示警告,而不是错误。由于您从with块返回,因此未使用try块中的表达式,因此您只需添加|> ignore即可。在这种情况下,如果fn抛出,则返回值为m.Zero(),唯一的区别是您将获得"hello world"而不是ex.Message。下面举例说明。完整的脚本:http://dotnetfiddle.net/mFbeZg

使用|> ignore进行原始实施以使警告静音:

let Combine3 p1 p2 fn =
            fun a -> res { let! x = p1 a
                           let! y = p2 a

                           try
                                return fn(x,y)
                           with
                                | ex -> Failure(ex.Message) |> ignore // no warning
                         }

运行它:

let comb2 a  =
    let p1' x = Success(x)
    let p2' y = Success(y)
    let fn' (x,y) = 1/0 // div by zero
    let func = Parser.Combine2 p1' p2' fn' a
    func()

let comb3 a  =
    let p1' x = Success(x)
    let p2' y = Success(y)
    let fn' (x,y) = 1/0 // div by zero
    let func = Parser.Combine3 p1' p2' fn' a
    func()

let test2 = comb2 1
let test3 = comb3 1

结果:

val test2 : Result<int> = Failure "Attempted to divide by zero."
val test3 : Result<int> = Failure "hello world"

答案 1 :(得分:2)

如果您想在计算构建器中支持try ... with,则需要添加TryWith(正如您所尝试的)以及其他一些成员,包括{{ 1}}和Delay(取决于您希望如何实现Run)。为了能够返回失败,您还需要通过添加Delay来支持return!

ReturnFrom

现在您可以执行以下操作:

type ResultBuilder() =
    member m.Return a = Success(a)
    member m.Bind(r,fn) =
        match r with
        | Success(a) -> fn a
        | Failure(m) -> Failure(m)
    member m.TryWith(r,fn) =
      try r() with ex -> fn ex
    member m.Delay(f) = f
    member m.Run(f) = f()
    member m.ReturnFrom(r) = r

诀窍是普通分支仅使用let Combine2 p1 p2 fn = fun a -> res { let! x = p1 a let! y = p2 a try return fn(x,y) with ex -> return! Failure(ex.Message) } (表示成功),但异常处理程序使用return使用return!返回显式创建的结果。

也就是说,如果你对解析器感兴趣,那么你需要使用不同的类型 - 你在这里描述的更像是选项(或者可能)monad。要实现解析器组合器,您需要一个表示解析器的类型,而不是解析器的 result 。请参阅示例this article