3

我试图围绕单子以及如何在现实世界的示例中使用它们。我给自己设定的第一个“任务”是编写一个“异常单子”,当然(此时)它只不过是为了满足我的目的而扭曲的“任一单子”。

我的代码如下所示:

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

    with
    static member returnM a =
        Success a

    static member bind f =
        fun e ->
            match e with
            | Success a -> f a
            | Failure m -> Failure m

    static member map f =
        fun e ->
            match e with
            | Success a -> Success (f a)
            | Failure m -> Failure m

// Create a little test case to test my code
let divide (n, m) =
    match m with
    | 0 -> Failure "Cannot divide by zero"
    | _ -> Success ((float n) / (float m))

let round (f:float) =
    Success ( System.Math.Round(f, 3) )

let toString (f:float) =
    sprintf "%f" f

let divideRoundAndPrintNumber = 
    divide
    >> MException<_>.bind round 
    >> MException<_>.map toString

// write the result
let result = divideRoundAndPrintNumber (11, 3)
match result with
| Success r -> printf "%s\n" r
| Failure m -> printf "%s\n" m

我的问题如下:divide 函数现在需要一个元组。我可以或应该做些什么来使绑定和映射函数对于具有多个参数的函数的行为正确?

编辑 30-12-2015:@Mark Seemann 的答案和评论都有助于找到问题的答案。@Mikhail 提供了解决方案的实现。柯里化是解决问题的正确方法。计算表达式不是一种解决方案,而是一种语法抽象,它确实有效,但一旦您将异步和其他模式添加到问题中,它就会变得复杂。“简单”的组合似乎是最简单和“最真实”的解决方案。

4

4 回答 4

4

更改divideRoundAndPrintNumber为函数而不是值

let divide n m =
    match m with
    | 0 -> Failure "Cannot divide by zero"
    | _ -> Success ((float n) / (float m))

let divideRoundAndPrintNumber n = 
    divide n
    >> MException<_>.bind round
    >> MException<_>.map toString
于 2015-12-30T00:04:51.553 回答
2

Monad 具有相当严格的结构要求,它们必须具有:

返回:'a -> m<'a>

绑定:m<'a> -> ('a -> m<'b>) -> m<'b>

您的divide 函数具有签名int*int -> MException<float>,即它确实具有'a -> m<'b>与bind 一起使用的所需形式。当与 bind 一起使用时,它将作用于某种类型的东西MException<int*int>并产生一个MException<float>.

如果divide是而不是类型int -> int -> MException<float>(即'a -> 'b -> m<'c>'),我们不能直接将它与绑定一起使用。我们可以做的是解开元组,然后一个一个地提供参数以创建一个具有正确形式的 lambda。

让我们添加一个额外的Return,以便我们可以更清楚地看到在这些约束中处理函数的一些不同方法:

let divideTupled (n, m) =
    match m with
    | 0 -> Failure "Cannot divide by zero"
    | _ -> Success ((float n) / (float m))

let divideRoundAndPrintNumber n m =
    MException<_>.Return (n,m) 
    |> MException<_>.Bind divideTupled
    |> MException<_>.Bind round 
    |> MException<_>.Map toString

或者

let divideCurried n m =
    match m with
    | 0 -> Failure "Cannot divide by zero"
    | _ -> Success ((float n) / (float m))

let divideRoundAndPrintNumber n m =
    MException<_>.Return (n,m) 
    |> MException<_>.Bind (fun (n,m) -> divideCurried n m)
    |> MException<_>.Bind round 
    |> MException<_>.Map toString

正如 Olaf 所提到的,计算表达式为在 F# 中使用 monad 提供了一些很好的语法糖。

于 2015-12-29T23:58:10.163 回答
2

为什么divide不像平常那​​样定义呢?

let divide n m =
    match m with
    | 0 -> Failure "Cannot divide by zero"
    | _ -> Success ((float n) / (float m))

然后你可以divideRoundAndPrintNumber像这样定义,同样以咖喱形式:

let divideRoundAndPrintNumber n m = 
    divide n m
    |> MException<_>.bind round 
    |> MException<_>.map toString

FSI 临时测试:

> let result = divideRoundAndPrintNumber 11 3;;    
val result : MException<string> = Success "3.667000"

> let result = divideRoundAndPrintNumber 11 0;;    
val result : MException<string> = Failure "Cannot divide by zero"
于 2015-12-30T00:25:56.883 回答
2

不幸的是,我对 F# 的了解不够,无法完全理解您的代码。例如,我不理解 >> 运算符和 MException<_> 表达式。但我可以为您的问题提供替代解决方案。它利用称为“计算表达式”的 F# 功能。它使您能够以类似 F# 的方式执行“Monadic”魔术:

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

type ExceptionBuilder() =

    member this.Bind (m, f) =
        match m with
        | Success a -> f a
        | Failure m -> Failure m

    member this.Return (x) =
        Success (x)

let ex = new ExceptionBuilder()

let divide n m =
    if m = 0 then Failure "Cannot divide by zero"
             else Success ((float n)/(float m))

let round (f : float) =
    Success (System.Math.Round(f, 3))

let divideRoundAndPrintNumber a b =
    ex {
        let! c = divide a b
        let! d = round c
        printf "result of divideRoundAndPrintNumber: %f\n" d
        return d
    }

let result = divideRoundAndPrintNumber 11 0
match result with
| Success r -> printf "%f\n" r
| Failure m -> printf "%s\n" m

当我的回答与您的问题不完全匹配时,我深表歉意,但我希望它有所帮助。

在这里,您可以找到有关此主题的优秀博客文章系列:

http://fsharpforfunandprofit.com/posts/computation-expressions-intro/

我还发现这篇文章很有启发性:

http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html

于 2015-12-29T22:20:49.657 回答