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.tryFind
、Map.tryFind
、Array.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)
F# 语言包含区分联合类型 option<'T>
。几个模块包含有用的函数 XYZ.tryFind
,其 return 值是类型 option<'T>
的对象。 (示例:List.tryFind
、Map.tryFind
、Array.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)