(由于未能"摸索"FParsec,我遵循了我在某处读到的建议,开始尝试自己编写一个小解析器。不知何故,我发现了一个尝试将其monadization的机会,现在我有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
with
块还应返回计算表达式的结果。由于要返回 Result.Failure,因此需要定义成员m.ReturnFrom a = a
并使用它来从 with
块返回 Failure。在try
块中,还应指定 fn
如果未抛出,则返回成功。
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)
}
更新:
原始实现显示警告,而不是错误。由于您从try
块返回,因此未使用with
块中的表达式,因此您只需添加|> 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"
如果你想支持try
... with
计算生成器中,您需要添加TryWith
(如您尝试的那样)以及其他一些成员,包括Delay
和Run
(取决于您希望如何实现Delay
)。为了能够返回失败,您还需要通过添加ReturnFrom
来支持return!
:
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
显式创建的结果。
也就是说,如果您对解析器感兴趣,那么您需要使用不同的类型 - 您在这里描述的更像是选项(或可能)monad。若要实现解析器组合器,您需要一个表示解析器而不是解析器结果的类型。例如,请参阅本文。