pha*_*haz 5 mapping f# list short-circuiting partial-functions
所以,我把这个函数叫做tryMap,如下所示:
/// tryMap, with failure and success continuations.
let rec tryMapC : 'R -> ('U list -> 'R) -> ('T -> 'U option) -> ('T list) -> 'R =
fun failure success mapping list ->
match list with
| [] -> success []
| head::tail ->
match mapping head with
| None -> failure
| Some result ->
let success = (fun results -> result::results |> success)
tryMapC failure success mapping tail
/// <summary>
/// Attempts to map a list with a partial function.
/// <para/>
/// If a value which maps to None is encountered,
/// the mapping stops, and returns None.
/// <para/>
/// Else, Some(list), containing the mapped values, is returned.
/// </summary>
let tryMap : ('T -> 'U option) -> 'T list -> 'U list option =
fun mapping list ->
tryMapC None Some mapping list
Run Code Online (Sandbox Code Playgroud)
其文档中描述的目的是使用部分函数映射列表,如果"完整"映射不是"可能的",则由于缺少更好的单词而使用简短的curcuit.
以下是如何使用它的示例:
鉴于此功能......
let tryFac n =
do printf "The factorial of %d" n
if n < 0 then
do printf " cannot be computed.\n"
None
else
let result = (List.fold (*) 1 [1..n])
do printf " is %d\n" result
Some result
Run Code Online (Sandbox Code Playgroud)
...我们现在可以使用tryMap对整数列表进行全有或全无映射,如下所示:
> let all = tryMap tryFac [1..5];;
The factorial of 1 is 1
The factorial of 2 is 2
The factorial of 3 is 6
The factorial of 4 is 24
The factorial of 5 is 120
val all : int list option = Some [1; 2; 6; 24; 120]
> let nothing = tryMap tryFac [1;2;-3;4;5];;
The factorial of 1 is 1
The factorial of 2 is 2
The factorial of -3 cannot be computed.
val nothing : int list option = None
Run Code Online (Sandbox Code Playgroud)
之后,很容易,例如,计算这些值的总和 - 如果它们都可以计算,那就是.
现在,我的问题是:
有没有更简单/更简洁的方法来实现这个tryMap函数?(当然,除了不那么冗长之外.:-P)我有一种感觉,可以使用列表表达式,也许是表达式(来自FSharpx)或者两者的组合来完成一些聪明的事情,但我无法弄清楚目前怎么样 : - /
PS:如果你有一个比'tryMap'更好的名字来做这个功能,请不要犹豫,发表评论.:-)
更新1:
我已经想出了这个版本,它与我的想法非常接近,除了遗憾的是它没有短路.: - /
let tryMap : ('T -> 'U option) -> 'T list -> 'U list option =
fun mapping list -> maybe { for value in list do return mapping value }
Run Code Online (Sandbox Code Playgroud)
注意:这使用FSharpx的'maybe-expressions.
更新2:
谢谢Tomas Petricek,我想到了一个替代方案:
let tryMap (mapping : 'T -> 'U option) (list : 'T list) : 'U list option =
List.fold
(
fun (cont,quit) value ->
if quit then
(cont,quit)
else
match mapping value with
| None -> (cont,true)
| Some r -> ((fun rs -> (r::rs)) >> cont,quit)
)
(id,false)
list
|> (fun (cont,quit) -> if quit then None else Some (cont []))
Run Code Online (Sandbox Code Playgroud)
此函数在映射到第一个None值后停止映射.当发生这种情况时,quit将会true,并且不会映射其余元素.之后,如果quit是true,则丢弃部分映射的列表并None返回.如果它从未映射到None,它将最终构建一个构建映射列表的延续.
它仍然很大,现在它只是做一个"轻"短路,从它停止尝试映射列表,但它仍然遍历它,因为这是折叠的工作方式.: - /
根据 Daniel 和 S\xc3\xb8ren Debois 的回答和评论,我得出以下结论:
\n\nlet tryMap f xs =\n let rec loop ys = function\n | [] -> maybe { return List.rev ys }\n | x::xs -> maybe { let! y = f x in return! loop (y::ys) xs }\n loop [] xs\nRun Code Online (Sandbox Code Playgroud)\n\n我不太确定它是否是尾递归,因为我不是计算(或者在这种情况下特别是:也许)表达式内部工作的专家。
\n\n大家对此有何看法?:-)
\n