在计算表达式中争论 trywith



(由于未能"摸索"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(如您尝试的那样)以及其他一些成员,包括DelayRun(取决于您希望如何实现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。若要实现解析器组合器,您需要一个表示解析器而不是解析器结果的类型。例如,请参阅本文。

相关内容

  • 没有找到相关文章

最新更新