由於@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
總是有一個值是否是好還是壞。如果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,叉(葉(消息 「哦鼻子」),葉(消息「該死它,伯伯叔叔!「)))
即,我們得到了一個不好的值1
,其原因很糟糕是因爲兩個加入的錯誤葉子。
我在使用可組合組合器轉換和驗證樹結構時使用了這種方法。在我看來,重要的是讓所有驗證失敗,而不僅僅是第一次。這意味着需要評估Bind
中的兩個分支,但爲了做到這一點,我們必須始終有一個值才能撥打uf
的Bind t uf
。
如OP:自己的答案,我做了實驗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
我,你打算什麼真實的生活場景來使用呢?你不需要一個Writer Monad嗎? – Gustavo
這是非常接近現實世界的情況,除了'let! a =成功1'會成爲'let! a = someFnReturningAResult With validationFn someValue' – robkuz
它在我看來,你正試圖積累驗證錯誤,如果是的話,作家Monad可能是要走的路。 – Gustavo