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

List 用部分函数短路列表映射,list,f#,mapping,short-circuiting,partial-functions,List,F#,Mapping,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

因此,我创建了一个名为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
例如,之后很容易计算这些值的总和——如果它们都可以计算的话,也就是说

现在,我的问题是:

/// 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
是否有更简单/更干净的方法来实现此tryMap功能?(当然,除了不那么冗长之外。:-P) 我有一种感觉,可以使用列表表达式,可能是表达式(来自FSharpx)或两者的组合来完成一些智能操作,但目前我还不太清楚如何实现:-/

PS:如果您对该函数有比“tryMap”更好的名称,请随时发表评论。:-)

更新1:

/// 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
我已经想出了这个版本,这是非常接近我的想法,除了它不幸没有短路-/

let tryMap : ('T -> 'U option) -> 'T list -> 'U list option = 
    fun mapping list -> maybe { for value in list do return mapping value }
注意:这使用FSharpx'maybe表达式

更新2:

/// 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
多亏了托马斯·佩特里切克(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
,那么它将构建一个用于构建映射列表的延续


虽然它仍然很大,但现在它只做了一个“轻”短路,从某种意义上说,它不再试图映射列表,但它仍然遍历列表,因为折叠就是这样工作的-/

我提出了一个使用
Seq.scan
Seq.takeWhile
的实现,它比较短,但不是特别优雅。它使用
Seq
作为它的惰性-这样,我们就不会对不需要的值运行计算,因为以前的一些函数失败了

其思想是产生一系列中间状态-因此我们将从
一些[]
开始,然后是
Some[first]
Some[second;first]
等等,当某些功能失败时,最后可能是
None
。它将新值附加到前面-这很好,因为我们可以在以后轻松地反转列表。使用
tryFac
作为调用函数,如下所示:

> 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
[1;2;-3;4;5] 
|> Seq.scan (fun (prev, st) v ->
  // If we failed when calculating previous function, return `None`
  match st with
  | None -> st, None
  | Some l ->
    // Otherwise run the function and see if it worked
    match tryFac v with
    | Some n -> st, Some (n::l) // If so, add new value
    | _ ->      st, None )      // Otherwise, return None
    (Some[], Some[]) // Using empty list as the initial state
  // Take while the previous value was 'Some'
  |> Seq.takeWhile (function Some _, _ -> true | _ -> false)
  // Take the last value we got and reverse the list
  |> Seq.last |> snd |> Option.map List.rev
状态不仅仅是当前值,而是包含上一个值和当前值的一对。这样,我们可以使用
takeWhile
轻松获得第一个
None
或最后一个
Some


编辑:嗯,我第一次写它时它比较短,但有了注释和更好的格式,它可能和您原来的函数一样长。因此,这可能不是一个真正的改进:-)

这里有一个单体/工作流样式的变体:

let rec tryMap f = function 
    | [] -> Some [] 
    | x :: xs -> f x         |> Option.bind (fun x' -> 
                 tryMap f xs |> Option.bind (fun xs' -> 
                 x' :: xs'   |> Some))
Option.bind f x
将函数
f
应用于选项
x
中的值(如果有),否则返回
None
。这意味着我们得到了所需的短路:只要
fx
返回None,
tryMap
返回

如果我们导入FSharpx的
maybe
monad,我们就可以使用工作流语法:

let maybe = FSharpx.Option.maybe
let rec tryMap2 f = function 
    | [] -> maybe { return [] }
    | x :: xs -> maybe { let! x' = f x
                         let! xs' = tryMap2 f xs 
                         return x' :: xs' }

这是一种简单的方法:

let tryMap f xs =
    let rec loop ys = function
        | [] -> Some (List.rev ys)
        | x::xs ->
            match f x with
            | None -> None
            | Some y -> loop (y::ys) xs
    loop [] xs

使用
选项.bind可以节省一些字符,但这读起来很好。

正如Mauricio Scheffer所指出的,这是更一般的遍历操作的一个特定实例。如果您对某些背景感兴趣,那么遍历操作是在可遍历的结构(如列表)上定义的,并支持映射到应用程序结构,其中选项是实例。如您所见,折叠列表和遍历列表之间有相似之处。这种相似性是由于幺半群和应用程序之间的关系

为了回答您的问题,下面是一个特定于list(可遍历)和option(应用程序)的实现。在这里,遍历是通过事后映射按顺序定义的。这简化了实现

module List =

    let rec sequenceO (ls:list<'a option>) : list<'a> option =
        match ls with
        | [] -> Some []
        | x::xs -> 
            match x with
            | Some x -> sequenceO xs |> Option.map (fun ls -> x::ls)
            | None -> None

    let traverseO (f:'a -> 'b option) (ls:list<'a>) : list<'b> option =
        sequenceO (List.map f ls) 
模块列表=
让rec sequenceO(ls:列表选项=
匹配
|[]->一些[]
|x::xs->
将x与
|一些x->sequenceO xs |>Option.map(有趣的ls->x::ls)
|无->无
让traverseO(f:'a->'b选项)(ls:列表选项=
sequenceO(List.map f ls)

根据Daniel和Søren Debois的回答和评论,我得出以下结论:

let tryMap f xs =
    let rec loop ys = function
        | []    -> maybe { return List.rev ys }
        | x::xs -> maybe { let! y = f x in return! loop (y::ys) xs }
    loop [] xs
我不太确定它是否是尾部递归的,因为我不是计算(或者在本例中特别是:可能)表达式内部工作的专家


你们对此有何看法?:-)

似乎你们重新发明了
mapM
/
traverse
(两者都是以一种更通用的方式定义的)哈。有趣。我确实认为这种模式似乎很熟悉。:-)你能举个例子说明如何使用mapM/traverse来“解决问题”?@Phazyck查看我的答案有趣的东西。:-)但是,您提供的实现并没有短路,是吗?确实如此。将printfn放在
sequenceO
函数的顶部,然后运行
[Some 1;None;Some 2]
哦,对了。但是traverseO不会使映射短路。:-)真的!一个基于
Seq
的impl是可以的。是的,这种区别似乎来自于懒惰和严格的评估。这看起来相当整洁。不过,我也希望避免在之后颠倒列表-这就是我的第一个实现使用continuations的原因。:-)为什么要避免颠倒列表?如果您关心性能,请查看针对延续的基准测试。这看起来非常好!是否也可以使其尾部递归?:-)当然可以,但是你会得到@Daniel的解决方案,使用
选项。bind
而不是他的ne