当谓词计算为 true 时,F# 停止 Seq.map。

7

我目前正在以类似的方式生成一个序列:

migrators
|> Seq.map (fun m -> m())

migrator函数最终返回一个区分联合类型,例如:

type MigratorResult =
| Success of string * TimeSpan
| Error of string * Exception

我希望在遇到第一个Error时停止map,但我需要在最终序列中包含Error
我有以下代码来向用户显示最终消息。
match results |> List.rev with
| [] -> "No results equals no migrators"
| head :: _ ->
   match head with
   | Success (dt, t) -> "All migrators succeeded"
   | Error (dt, ex) -> "Migration halted owing to error"

所以我需要:

  1. 当映射步骤中的一个产生 Error 时停止映射的方法
  2. 将该错误作为最终添加到序列中的元素的方法

我知道可能有其他顺序方法可以做到这一点,但我对 F# 还不熟悉,在网上搜索还没有得到任何结果!

3个回答

6

我猜这里有多种方法,但其中一种方法是使用unfold:

migrators 
|> Seq.unfold (fun ms ->
    match ms with
    | m :: tl -> 
        match m () with
        | Success res -> Some (Success res, tl)
        | Error res   -> Some (Error res, [])
    | [] -> None)
|> List.ofSeq

注意最后的List.ofSeq,它只是用于实现序列。另一种方法是使用序列推导式,有些人认为这会产生更清晰的代码。

这真的很好用!我知道它将是一个带有累加器的函数之一,但没有意识到可以这样使用unfold。 - Clint

3

Tomaš所提到的丑陋之处是1)可变状态,和2)对底层枚举器的操纵。一个返回满足条件的元素及其之前所有元素的高阶函数应该长这样:

module Seq =
    let takeUntil pred (xs : _ seq) = seq{
        use en = xs.GetEnumerator()
        let flag = ref true
        while !flag && en.MoveNext() do
            flag := not <| pred en.Current
            yield en.Current }

seq{1..10} |> Seq.takeUntil (fun x -> x % 5 = 0)
|> Seq.toList
// val it : int list = [1; 2; 3; 4; 5]

针对您的特定应用程序,您需要将DU的情况映射到布尔值。

(migrators : seq<MigratorResult>)
|> Seq.takeUntil (function Success _ -> false | Error _ -> true)

2
我认为@scrwtp的答案可能是最好的方法,如果您的输入比较小(并且您可以将其转换为F#列表以使用模式匹配)。我将添加另一个版本,适用于当您的输入仅为序列时,并且您不想将其转换为列表的情况。
基本上,您想要做的事情几乎像是Seq.takeWhile,但它会在末尾提供一个附加项(即谓词失败的那个项)。
举个简单的例子,以下代码返回序列中所有数字,直到可被5整除的数字为止:
let nums = [ 2 .. 10 ]

nums
|> Seq.map (fun m -> m % 5)
|> Seq.takeWhile (fun n -> n <> 0)

所以,基本上你只需要向前看一个元素 - 为了做到这一点,你可以使用Seq.pairwise,它会给你当前元素和序列中的下一个元素。

nums
|> Seq.map (fun m -> m % 5)
|> Seq.pairwise                          // Get sequence of pairs with the next value
|> Seq.takeWhile (fun (p, n) -> p <> 0)  // Look at the next value for test
|> Seq.mapi (fun i (p, n) ->             // For the first item, we return both
    if i = 0 then [p;n] else [n])        //   for all other, we return the second
|> Seq.concat

这里唯一不美的地方是你需要使用 mapiconcat 再次压平序列。
这并不太好,所以一个好的做法是定义自己的高阶函数,比如 Seq.takeUntilAfter,封装你需要的行为(并隐藏所有丑陋的东西)。然后你的代码可以只使用该函数,并且看起来漂亮易读(你还可以尝试其他实现方式)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接