也许monad允许链接一组所有可能失败的运算符(通过返回None),如果每个子操作成功,则返回Some result
,如果有任何失败,则返回None
。这是一个小例子:
type MaybeBuilder() =
member this.Return(x) =
Some x
member this.Bind(m, f) =
match m with
| Some x -> f x
| None -> None
let maybe = MaybeBuilder()
let list = [1;2;3;4]
// evaluates to Some 3
maybe {
let! x1 = List.tryFind ((=) 1) list
let! x2 = List.tryFind ((=) 2) list
return x1 + x2
}
// evaluates to None
maybe {
let! x1 = List.tryFind ((=) 1) list
let! x2 = List.tryFind ((=) 6) list
return x1 + x2
}
这大致相当于:
// evaluates to Some 3
match List.tryFind ((=) 1) list with
| None -> None
| Some x1 ->
match List.tryFind ((=) 2) list with
| None -> None
| Some x2 -> Some (x1 + x2)
// evaluates to None
match List.tryFind ((=) 1) list with
| None -> None
| Some x1 ->
match List.tryFind ((=) 6) list with
| None -> None
| Some x2 -> Some (x1 + x2)
在我的一段代码中,我正在做与此相反的“反面”,返回第一个成功点击:
// evaluates to Some 1
match List.tryFind ((=) 1) list with
| Some x1 -> Some x1
| None ->
match List.tryFind ((=) 2) list with
| Some x2 -> Some x2
| None -> None
// evaluates to Some 2
match List.tryFind ((=) 6) list with
| Some x1 -> Some x1
| None ->
match List.tryFind ((=) 2) list with
| Some x2 -> Some x2
| None -> None
这也可以用monad来获得漂亮的计算表达式语法吗?
答案 0 :(得分:8)
前段时间,我写了一篇实施imperative computation builder in F#的博客。例如,以下是返回0并且从不执行printfn
语句的计算:
let test() = imperative {
return 0
printfn "after return!"
return 1 }
我认为您的代码示例可以写成:
imperative { return! List.tryFind ((=) 1) list
return! List.tryFind ((=) 2) list }
正如你所建议的那样(和Lee也提到过),类型也是基于option<'T>
类型,但是我使用的是一个较小的差异,我正在使用延迟的选项值(这样你就可以在不评估它们的情况下编写计算结果),所以monadic类型的类型实际上是:
type Imperative<'T> = unit -> option<'T>
计算构建器中的关键技巧是添加Combine
(其行为类似于Lee的Haskell版本中的mplus
),它运行第一次计算并返回其结果(如果它是Some
)或运行其余的(如果它是None
)(a
和b
实际上都是函数 - 所以我们需要调用它们并延迟结果):
member x.Combine(a, b) = (fun () ->
match a() with
| Some(v) -> Some(v) // if it returned, we can return the result immediately
| _ -> b() ) // otherwise, we need to run the second part
它实际上非常好用 - 你可以添加对循环的支持&amp;异常处理,如果您使类型更复杂,您可以添加其他功能,如break
:
imperative {
for x in 1 .. 5 do
if (x % 2 = 0) then do! continue
printfn "number = %d" x }
答案 1 :(得分:6)
Tomas'解决方案
imperative {
return! List.tryFind ((=) 1) list
return! List.tryFind ((=) 2) list }
做我想做的事,但我意识到我还可以通过这个简单地实现我所需要的:
// evaluates to Some 1
[1;2] |> List.tryPick (fun x -> List.tryFind ((=) x) list)
// evaluates to Some 2
[6;2] |> List.tryPick (fun x -> List.tryFind ((=) x) list)
答案 2 :(得分:5)
Haskell使用MonadPlus
类型定义为:
class Monad m => MonadPlus m where
mzero :: m a
mplus :: m a -> m a -> m a
Maybe
将此类型类实现为
instance MonadPlus Maybe where
mzero = Nothing
Nothing `mplus` Nothing = Nothing
Just x `mplus` Nothing = Just x
Nothing `mplus` Just x = Just x
Just x `mplus` Just y = Just x
mzero
的{{1}}和mplus
成员似乎与F#计算表达式使用的MonadPlus
和Zero
成员相对应。
答案 3 :(得分:4)
您还可以定义一个简单的函数来执行此操作:
let orElse f = function
| None -> f()
| Some _ as x -> x
您可以将任意数量的函数链接在一起,第一个Some
结果将作为整个表达式的结果返回:
List.tryFind ((=) 1) list
|> orElse (fun () -> List.tryFind ((=) 2) list)
|> orElse (fun () -> List.tryFind ((=) 3) list)
|> orElse (fun () -> List.tryFind ((=) 4) list)
答案 4 :(得分:1)
这种特殊情况也可以用序列模拟:
let tests = seq {
yield List.tryFind ((=) 5) list
yield List.tryFind ((=) 3) list
yield List.tryFind ((=) 6) list
}
tests |> Seq.tryFind Option.isSome |> Option.bind id