Choice <'T1,'T2>上的Monadic操作

时间:2018-07-25 14:50:47

标签: f# computation-expression

我在标准库中找不到对象choice,这使我可以编写

let safeDiv (numer : Choice<Exception, int>) (denom : Choice<Exception, int>) =
    choice {
        let! n = numer
        let! d = denom
        return! if d = 0
            then Choice1Of2 (new DivideByZeroException())
            else Choice2Of2 (n / d)
    }

就像在Haskell中一样。我是否错过了任何东西,或者是否有第三方库来编写此类内容,还是我必须重新发明这个轮子?

2 个答案:

答案 0 :(得分:5)

Choice<'a,'b>类型没有内置的计算表达式。通常,F#没有为常用的Monad提供内置的计算表达式,但是它确实提供了一种相当简单的方法来自己创建它们:Computation Builders。 This series是一个很好的教程,介绍了如何自己实现它们。 F#库通常确实定义了一个bind函数,可以用作Computation Builder的基础,但对于Choice类型却没有一个函数(我怀疑是因为有很多变化Choice

根据您提供的示例,我怀疑F#Result<'a, 'error>类型实际上更适合您的情况。几个月前,有一个code-review用户在其中发布了Either计算生成器,并且如果您想利用它,则可以接受较为完整的答案。

答案 1 :(得分:1)

值得注意的是,与Haskell不同,使用异常是处理F#中异常情况的一种完全可接受的方法。语言和运行时都对异常提供了一流的支持,使用它们也没有错。

我知道您的safeDiv函数是用于说明,而不是一个实际问题,因此没有理由显示如何使用异常编写该函数。

在更现实的情况下:

  • 如果仅当实际发生错误(网络故障等)时才发生异常,那么我只想让系统抛出异常并在需要重新启动时使用try ... with进行处理工作或通知用户。

  • 如果异常表示预期的结果(例如无效的用户输入),则如果您定义自定义数据类型来表示错误的状态(而不是使用Choice<'a, exn>没有语义含义。)

还值得注意的是,仅当您需要将特殊行为(异常传播)与普通计算混合时,计算表达式才有用。我认为通常希望尽可能避免这种情况(因为它会使效果与纯计算交错)。

例如,如果要进行输入验证,则可以定义以下内容:

let result = validateAll [ condition1; condition2; condition3 ]

相对于计算表达式,我更愿意这样做:

let result = validate {
  do! condition1
  do! condition2
  do! condition3 }

也就是说,如果您绝对确定需要用于错误传播的自定义计算生成器,那么Aaron的答案将提供您所需的所有信息。