F#语言包含Discriminated Union类型option<'T>
。有几个模块包含有用函数XYZ.tryFind
,其返回值是option<'T>
类型的对象。 (示例:List.tryFind
,Map.tryFind
,Array.tryFind
)。 F# 4.1
添加了与Result<'S,'T>
类似的option<'T>
类型,但提供了更多信息。对于tryFind
类型,是否有与Result<'S,'T>
类似的功能?
以下代码是尝试创建此类功能。
let resultFind (ef: 'K -> 'T) (tryfind: 'K -> 'M -> 'T option) (m: 'M) (k: 'K) =
let y = tryfind k m
match y with
| Some i -> Result.Ok i
| None -> Result.Error (ef k)
let fields = [("Email", "jdoe@xyz.com"); ("Name", "John Doe")]
let myMap = fields |> Map.ofList
let ef k = sprintf "%s %A" "Map.tryFind called on myMap with bad argument " k
let rF = resultFind ef Map.tryFind myMap // analogous to tryFind
rF "Name"
rF "Whatever"
val resultFind :
ef:('K -> 'T) ->
tryfind:('K -> 'M -> 'T option) -> m:'M -> k:'K -> Result<'T,'T>
val fields : (string * string) list =
[("Email", "jdoe@xyz.com"); ("Name", "John Doe")]
val myMap : Map<string,string> =
map [("Email", "jdoe@xyz.com"); ("Name", "John Doe")]
val ef : k:'a -> string
val rF : (string -> Result<string,string>)
[<Struct>]
val it : Result<string,string> = Ok "John Doe"
[<Struct>]
val it : Result<string,string> =
Error "Map.tryFind called on myMap with bad argument "Whatever""
另外,为什么[<Struct>]
声明会出现在Result
个对象上方?
答案 0 :(得分:9)
标准库没有这些功能,也不应该。使用像tryFind
这样的函数,实际上只有一件事可能出错:无法找到价值。因此,实际上不需要全面表示错误情况。只是一个简单的&#34;是/否&#34;信号就够了。
但是,在已知的上下文中,您需要&#34;标记&#34;这是一个合法的用例。具有特定错误信息的失败,以便您可以将其传递给更高级别的消费者。
但是,对于这个用例,为每个函数创建一个包装器将是浪费和重复:除了它们调用的函数之外,这些包装器将完全相同。通过使你的函数成为一个更高阶的函数,你的尝试是正确的方向,但它还远远不够:即使你已经接受了函数作为参数,你已经&#34;烘烤了&#34; 34;该函数的形状。当您发现自己需要使用两个参数的功能时,您必须复制并粘贴包装器。这最终来自于您的函数负责两个方面 - 调用函数和转换结果。你不能重用另一个。
让我们尝试进一步扩展方法:将问题分解成更小的部分,然后将它们组合在一起以获得完整的解决方案。
首先,让我们自己创造一种方式来转换&#34;将Option
值转换为Result
个值。显然,我们需要提供错误的值:
module Result =
let ofOption (err: 'E) (v: 'T option) =
match v with
| Some x -> Ok x
| None -> Error err
现在我们可以使用它将Option
转换为Result
:
let r = someMap |> Map.tryFind k |>
Result.ofOption (sprintf "Key %A couldn't be found" k)
到目前为止一切顺利。但接下来要注意的是,并不总是需要错误值,因此每次计算它都会很浪费。让我们推迟这个计算:
module Result =
let ofOptionWith (err: unit -> 'E) (v: 'T option) =
match v with
| Some x -> Ok x
| None -> Error (err())
let ofOption (err: 'E) = ofOptionWith (fun() -> err)
现在,当错误值计算成本低时,我们仍然可以使用ofOption
,但我们也可以使用ofOptionWith
延迟计算:
let r = someMap |> Map.tryFind k
|> Result.ofOptionWith (fun() -> sprintf "Key %A couldn't be found" k)
接下来,我们可以使用此转换函数创建包含返回Option
的函数的包装器,以使它们返回Result
:
module Result =
...
let mapOptionWith (err: 'a -> 'E) (f: 'a -> 'T option) a =
f a |> ofOptionWith (fun() -> err a)
现在,我们可以根据rF
:
Result.mapOptionWith
函数
let rF = Result.mapOptionWith
(sprintf "Map.tryFind called on myMap with bad argument %s")
(fun k -> Map.tryFind k myMap)