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

时间:2017-05-06 18:01:55

标签: f# optional f#-4.1

F#语言包含Discriminated Union类型option<'T>。有几个模块包含有用函数XYZ.tryFind,其返回值是option<'T>类型的对象。 (示例:List.tryFindMap.tryFindArray.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个对象上方?

1 个答案:

答案 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)