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

Sol*_*lma 4 f# optional f#-4.1

F#语言包含Discriminated Union类型option<'T>.几个模块包含有用的函数,XYZ.tryFind其返回值是类型的对象option<'T>.(实施例:List.tryFind,Map.tryFind,Array.tryFind).F# 4.1添加了Result<'S,'T>类似option<'T>但提供更多信息的类型.是否有类似于tryFindResult<'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"" 
Run Code Online (Sandbox Code Playgroud)

此外,为什么[<Struct>]声明出现在Result对象上方?

Fyo*_*kin 9

标准库没有这些功能,也不应该.有了这样的功能tryFind,实际上只有一件事可能出错:无法找到价值.因此,实际上不需要全面表示错误情况.只需一个简单的"是/否"信号即可.

但是,在已知的上下文中,您需要使用特定的错误信息"标记"失败,以便您可以将其传递给更高级别的使用者,这是一个合法的用例.

但是,对于这个用例,为每个函数创建一个包装器将是浪费和重复的:除了它们调用的函数之外,这些包装器将完全相同.通过使你的函数成为一个更高阶的函数,你的尝试是正确的方向,但它还远远不够:即使你接受函数作为参数,你已经"烘焙"了该函数的形状.当你发现自己需要使用两个参数的函数时,你必须复制并粘贴包装器.这最终来自于您的函数负责两个方面 - 调用函数和转换结果.没有另一个,你不能重复使用.

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

首先,让我们自己创造一种将Option价值"转换"为一种价值的方法Result.显然,我们需要提供错误的值:

module Result =
    let ofOption (err: 'E) (v: 'T option) =
        match v with
        | Some x -> Ok x
        | None -> Error err
Run Code Online (Sandbox Code Playgroud)

现在我们可以用它来转换OptionResult:

let r = someMap |> Map.tryFind k |> 
        Result.ofOption (sprintf "Key %A couldn't be found" k)
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.但接下来要注意的是,并不总是需要错误值,因此每次计算它都会很浪费.让我们推迟这个计算:

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)
Run Code Online (Sandbox Code Playgroud)

现在我们仍然可以ofOption在错误值计算成本时使用,但我们也可以使用ofOptionWith以下命令推迟计算:

let r = someMap |> Map.tryFind k 
        |> Result.ofOptionWith (fun() -> sprintf "Key %A couldn't be found" k)
Run Code Online (Sandbox Code Playgroud)

接下来,我们可以使用这个转换函数来创建返回函数的包装器Option,使它们返回Result:

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

现在我们可以rF根据以下方面定义您的功能Result.mapOptionWith:

let rF = Result.mapOptionWith
           (sprintf "Map.tryFind called on myMap with bad argument %s")
           (fun k -> Map.tryFind k myMap)
Run Code Online (Sandbox Code Playgroud)

  • 是的,翻转功能将是同一个方向.我不想提到这种可能性,因为我不知道你是否熟悉这个概念,并且不想不必要地重复解释.也就是说,我还必须提到我不建议将flip作为运算符实现:这将使你的代码对于新人来说更加混乱,甚至在一年内也会让你自己感到困惑. (2认同)