3

(未能“理解”FParsec,我按照我在某处读到的建议开始尝试自己编写一个小解析器。不知何故,我发现了一个尝试单子化它的机会,现在我有 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) }

不知道我应该在零中返回什么,我只是扔了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

4

2 回答 2

3

with块还应该从计算表达式返回一个结果。由于您要返回 Result.Failure ,因此您需要定义成员并使用它从块m.ReturnFrom a = a中返回失败。withtry块中,您还应该指定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. 在这种情况下,如果fnthrows 则返回值是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"
于 2014-02-15T20:21:31.127 回答
3

如果您想在计算构建器中支持try... with,您需要添加TryWith(如您尝试的那样)以及其他一些成员,包括Delayand 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!使用Failure.

也就是说,如果您对解析器感兴趣,那么您需要使用不同的类型 - 您在这里描述的更像是 option(或 Maybe)monad。要实现解析器组合器,您需要一个表示解析器而不是解析器结果的类型。例如见这篇文章

于 2014-02-15T21:51:01.143 回答