所以,我把这个函数叫做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
其文档中描述的目的是使用部分函数映射列表,如果“完整”映射不是“可能的”,则使用短曲线,因为缺少更好的单词。
以下是如何使用它的示例:
鉴于此功能......
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'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 []))
此函数在映射到第一个None
值后停止映射。发生这种情况时,quit
将为true
,其余元素将不会映射。之后,如果quit
为true
,则会丢弃部分映射的列表并返回None
。如果它从不映射到None
,它将最终构建一个构建映射列表的延续。
它仍然相当大,现在它只是做了一个“轻”短路,从它停止尝试映射列表,但它仍然遍历它,因为这是折叠的工作方式。 : - /
答案 0 :(得分:2)
我想出了一个使用Seq.scan
和Seq.takeWhile
的实现,它更短,但不是特别优雅。它使用Seq
来实现它的懒惰 - 这样,我们不会对我们不需要的值运行计算,因为一些早期的函数已经失败。
这个想法是产生一系列中间状态 - 所以我们将从Some []
开始,然后是
当某个函数失败时,Some [first]
和Some [second; first]
等等,最后可能None
。它将新值附加到前面 - 这很好,因为我们可以在以后轻松地反转列表。使用tryFac
作为调用函数,它看起来像这样:
[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
状态不仅仅是当前值,而是包含先前值和当前值的对。这样,我们可以使用None
轻松获取第一个Some
或最后takeWhile
值。
编辑:嗯,当我第一次写它时它更短,但是通过评论和更好的格式化,它可能与你原来的功能一样长。所以也许这不是一个改进: - )
答案 1 :(得分:2)
以下是monadic / workflow风格的变体:
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
。这意味着我们得到了所需的短路:只要f x
返回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' }
答案 2 :(得分:2)
这是一种直接的方法:
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
你可以使用Option.bind
保存一些字符,但这很好看。
答案 3 :(得分:2)
正如Mauricio Scheffer所指出的,这是一个更一般的遍历操作的特定实例。如果您对某些背景感兴趣,则遍历操作在可遍历的结构(例如列表)上定义,并支持映射到应用程序结构,其中选项是实例。如您所见,折叠列表和遍历列表之间存在相似之处。相似性是由于幺半群与应用之间的关系。
要回答您的问题,以下是特定于列表(可遍历)和选项(应用程序)的实现。这里,遍历是通过事后映射按顺序定义的。这简化了实施。
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)
答案 4 :(得分:1)
根据Daniel和SørenDebois的回答和评论,我想出了以下内容:
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
我不太确定它是否是尾递归的,因为我不是计算内部工作的全部专家 - (或者在这种情况下具体地说:也许 - )表达式。
你们怎么看待这个? : - )