我试图围绕monads以及如何在现实世界的例子中使用它们。第一个"任务"我设定自己就是写一个" Exception Monad"当然(在这一点上)只不过是"无论是monad"扭曲以适应我的目的。
我的代码如下所示:
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
我的问题如下:除法功能现在需要一个元组。对于具有多个参数的函数,可以或应该做什么来使绑定和映射函数正常运行?
编辑30-12-2015: @Mark Seemann的答案和评论都有助于找到问题的答案。 @Mikhail提供了解决方案的实施。 Currying是解决问题的正确方法。计算表达式不是一种解决方案,而是一种语法抽象,它可以工作但是一旦你为问题添加异步和其他模式就会变得复杂。 &#34;简单&#34;组成似乎是最简单的&#34;最真实的&#34;解。
答案 0 :(得分:4)
将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
更改为函数而不是值
a
答案 1 :(得分: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"
答案 2 :(得分:1)
不幸的是,我对F#不太了解,完全理解你的代码。例如,我不明白&gt;&gt;运算符和MException&lt; _&gt;表达。但我可以为您提供替代解决方案。它利用了名为&#34; Computation Expressions&#34;的F#功能。它使你能够做到&#34; Monadic&#34;神奇的F#方式:
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
答案 3 :(得分:1)
Monads具有相当严格的必需结构,它们必须具有:
返回:'a -> m<'a>
和
绑定:m<'a> -> ('a -> m<'b>) -> m<'b>
您的除法函数具有签名int*int -> MException<float>
,即它确实具有与bind一起使用的所需'a -> m<'b>
形式。当与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提供了一些很好的语法糖。