F# 4.1 中是否有类似于 tryFind 的新结果类型?

Is there an analogous to tryFind for the new Result type in F# 4.1?

F# 语言包含区分联合类型 option<'T>。几个模块包含有用的函数 XYZ.tryFind,其 return 值是类型 option<'T> 的对象。 (示例:List.tryFindMap.tryFindArray.tryFind)。 F# 4.1 添加了类似于 option<'T> 的类型 Result<'S,'T>,但提供了更多信息。 Result<'S,'T> 类型是否有类似于 tryFind 的函数?

下面的代码试图创建这样一个函数。

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 对象之上?

标准库没有这些函数,也不应该。对于像 tryFind 这样的函数,实际上只有一件事可能出错:找不到值。因此,真的没有必要对错误案例进行综合表示。只需一个简单的 "yes/no" 信号就足够了。

但这是一个合法的用例,在已知上下文中,您需要 "tag" 带有特定错误信息的故障,以便您可以将其传递给更高级别的消费者。

但是,对于这个用例,为每个函数发明一个包装器将是浪费和重复的:除了它们调用的函数之外,这些包装器将完全相同。你的尝试通过使你的函数成为高阶函数而朝着正确的方向前进,但它还不够远:即使你接受函数作为参数,你 "baked in" 该函数的 shape。当你发现自己需要使用一个函数时,比如说,两个参数,你将不得不复制并粘贴包装器。这最终来自于您的函数处理两个方面的事实 - 调用函数和转换结果。你不能重复使用其中之一。

让我们尝试进一步扩展该方法:将问题分解成更小的部分,然后将它们组合在一起以获得完整的解决方案。

首先,让我们自己发明一种将 "convert" 值 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)

接下来,我们可以使用此转换函数来围绕 return Option 函数创建包装器,使它们成为 return Result:

module Result =
    ...
    let mapOptionWith (err: 'a -> 'E) (f: 'a -> 'T option) a =
       f a |> ofOptionWith (fun() -> err a)

现在我们可以根据 Result.mapOptionWith:

定义您的 rF 函数
let rF = Result.mapOptionWith
           (sprintf "Map.tryFind called on myMap with bad argument %s")
           (fun k -> Map.tryFind k myMap)