List 用部分函数短路列表映射
因此,我创建了一个名为tryMap的函数,如下所示: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, 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