【发布时间】:2014-05-30 16:17:56
【问题描述】:
所以,我制作了这个名为 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
如其文档中所述,其目的是使用部分函数映射列表,如果“完整”映射不是“可能”,则使用简短的 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
...我们现在可以使用 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
然后,例如,计算这些值的总和将很容易 - 如果它们可以全部被计算出来,那就是。
现在,我的问题是:
有没有更简单/更简洁的方法来实现这个 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 }
注意:这使用了 FSharpx 的可能表达式。
更新 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 []))
此函数在映射到其第一个 None 值后停止映射。发生这种情况时,quit 将是 true,其余元素将不会被映射。之后,如果quit 是true,则丢弃部分映射的列表并返回None。如果它从不映射到None,它将最终构建一个用于构建映射列表的延续。
虽然它仍然很大,现在它只是做了一个“轻微”的短路,因为它停止尝试映射列表,但它仍然遍历它,因为这就是折叠的工作方式。 :-/
【问题讨论】:
-
看来你重新发明了
mapM/traverse。 (两者都在FSharpPlus 中以更通用的方式定义) -
嗯。有趣的。我确实认为这种模式似乎很熟悉。 :-) 你能举例说明如何使用 mapM / traverse 来“解决问题”吗?
-
@Phazyck 看看我的回答
标签: list f# mapping short-circuiting partial-functions