使用部分函数短路列表映射

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,并且不会映射其余元素.之后,如果quittrue,则丢弃部分映射的列表并None返回.如果它从未映射到None,它将最终构建一个构建映射列表的延续.

它仍然很大,现在它只是做一个"轻"短路,从它停止尝试映射列表,但它仍然遍历它,因为这是折叠的工作方式.: - /

pha*_*haz 1

根据 Daniel 和 S\xc3\xb8ren Debois 的回答和评论,我得出以下结论:

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

我不太确定它是否是尾递归,因为我不是计算(或者在这种情况下特别是:也许)表达式内部工作的专家。

\n\n

大家对此有何看法?:-)

\n