正如@tomasp 所说,一种方法是除了失败之外始终提供一个值,以便bind
正常工作。这是我在处理这个问题时一直使用的方法。然后,我会将其定义更改Result
为,例如:
type BadCause =
| Exception of exn
| Message of string
type BadTree =
| Empty
| Leaf of BadCause
| Fork of BadTree*BadTree
type [<Struct>] Result<'T> = Result of 'T*BadTree
这意味着Result
无论好坏,a 总是有一个值。BadTree
如果为空,则该值很好。
我更喜欢树而不是列表的原因是,Bind
它将聚合两个单独的结果,这些结果可能具有导致列表连接的子故障。
一些可以让我们创造好价值或坏价值的功能:
let rreturn v = Result (v, Empty)
let rbad bv bt = Result (bv, bt)
let rfailwith bv msg = rbad bv (Message msg |> Leaf)
因为即使是糟糕的结果也需要携带一个值才能Bind
工作,所以我们需要通过bv
参数提供该值。对于支持的类型,Zero
我们可以创建一个方便的方法:
let inline rfailwithz msg = rfailwith LanguagePrimitives.GenericZero<_> msg
Bind
易于实现:
let rbind (Result (tv, tbt)) uf =
let (Result (uv, ubt)) = uf tv
Result (uv, btjoin tbt ubt)
那是; 我们评估这两个结果并在需要时加入坏树。
使用计算表达式构建器,以下程序:
let r =
result {
let! a = rreturn 1
let! b = rfailwithz "Oh nose!"
let! c = rfailwithz "God damn it, uncle Bob!"
return a + b + c
}
printfn "%A" r
输出:
结果 (1,Fork (Leaf (Message "Oh nose!"),Leaf (Message "God fucking it, uncle Bob!")))
那是; 我们得到一个不好的值1
,它不好的原因是因为两个连接的错误叶子。
我在使用可组合组合器转换和验证树结构时使用了这种方法。在我的情况下,让所有验证失败都很重要,而不仅仅是第一次。这意味着Bind
需要评估 in 的两个分支,但为了这样做,我们必须始终有一个值才能调用uf
in Bind t uf
。
正如我在 OP:s own answer 中尝试过的一样,Unchecked.defaultof<_>
但我放弃了例如,因为字符串的默认值是null
并且它通常会在调用uf
. 我确实创建了一个地图Type -> empty value
,但在我的最终解决方案中,我在构建一个糟糕的结果时需要一个糟糕的值。
希望这可以帮助
完整示例:
type BadCause =
| Exception of exn
| Message of string
type BadTree =
| Empty
| Leaf of BadCause
| Fork of BadTree*BadTree
type [<Struct>] Result<'T> = Result of 'T*BadTree
let (|Good|Bad|) (Result (v, bt)) =
let ra = ResizeArray 16
let rec loop bt =
match bt with
| Empty -> ()
| Leaf bc -> ra.Add bc |> ignore
| Fork (l, r) -> loop l; loop r
loop bt
if ra.Count = 0 then
Good v
else
Bad (ra.ToArray ())
module Result =
let btjoin l r =
match l, r with
| Empty , _ -> r
| _ , Empty -> l
| _ , _ -> Fork (l, r)
let rreturn v = Result (v, Empty)
let rbad bv bt = Result (bv, bt)
let rfailwith bv msg = rbad bv (Message msg |> Leaf)
let inline rfailwithz msg = rfailwith LanguagePrimitives.GenericZero<_> msg
let rbind (Result (tv, tbt)) uf =
let (Result (uv, ubt)) = uf tv
Result (uv, btjoin tbt ubt)
type ResultBuilder () =
member x.Bind (t, uf) = rbind t uf
member x.Return v = rreturn v
member x.ReturnFrom r = r : Result<_>
let result = Result.ResultBuilder ()
open Result
[<EntryPoint>]
let main argv =
let r =
result {
let! a = rreturn 1
let! b = rfailwithz "Oh nose!"
let! c = rfailwithz "God damn it, uncle Bob!"
return a + b + c
}
match r with
| Good v -> printfn "Good: %A" v
| Bad es -> printfn "Bad: %A" es
0