简体   繁体   English

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

[英]F# stop Seq.map when a predicate evaluates true

I'm currently generating a sequence in a similar way to: 我正在以类似的方式生成序列:

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

The migrator function is ultimately returning a discriminated union like: migrator功能最终会返回一个受歧视的联合,如:

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

I want to stop the map once I encounter my first Error but I need to include the Error in the final sequence. 我想在遇到第一个Error停止map ,但我需要在最终序列中包含Error

I have something like the following to display a final message to the user 我有以下内容向用户显示最终消息

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"

So I need: 所以我需要:

  1. A way to stop the mapping when one of the map steps produces an Error 一种映射步骤生成Error时停止映射的方法
  2. A way to have that error be the final element added to the sequence 将错误作为添加到序列的最后一个元素的方法

I appreciate there may be a different sequence method other than map that will do this, I'm new to F# and searching online hasn't yielded anything as yet! 我很欣赏除了map之外可能会有一个不同的序列方法,我是F#的新手并且在线搜索还没有产生任何东西!

I guess there are multiple approaches here, but one way would be to use 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

Note the List.ofSeq at the end, that's just there for realizing the sequence. 注意最后的List.ofSeq ,就在那里实现序列。 A different way to go would be to use sequence comprehensions, some might say it results in a clearer code. 另一种方法是使用序列理解,有些人可能会说它会产生更清晰的代码。

The ugly things Tomaš alludes to are 1) mutable state, and 2) manipulation of the underlying enumerator. Tomaš暗示的丑陋事物是1)可变状态,2)操纵底层枚举器。 A higher-order function which returns up to and including when the predicate holds would then look like this: 返回到包括谓词保持时的高阶函数将如下所示:

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]

For your specific application, you'd map the cases of the DU to a boolean. 对于您的特定应用程序,您可以将DU的大小写映射到布尔值。

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

I think the answer from @scrwtp is probably the nicest way to do this if your input is reasonably small (and you can turn it into an F# list to use pattern matching). 我认为@scrwtp的答案可能是最好的方法,如果您的输入相当小(并且您可以将其转换为F#列表以使用模式匹配)。 I'll add one more version, which works when your input is just a sequence and you do not want to turn it into a list. 我将再添加一个版本,当您的输入只是一个序列并且您不想将其转换为列表时,该版本有效。

Essentially, you want to do something that's almost like Seq.takeWhile , but it gives you one additional item at the end (the one, for which the predicate fails). 从本质上讲,你想要做一些几乎像Seq.takeWhile ,但它最后会给你一个额外的项目(谓词失败的那个)。

To use a simpler example, the following returns all numbers from a sequence until one that is divisible by 5: 要使用更简单的示例,以下内容将返回序列中的所有数字,直到可被5整除的数字:

let nums = [ 2 .. 10 ]

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

So, you basically just need to look one element ahead - to do this, you could use Seq.pairwise which gives you the current and the next element in the sequence" 所以,你基本上只需要提前看一个元素 - 为此,你可以使用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

The only ugly thing here is that you then need to flatten the sequence again using mapi and concat . 这里唯一的丑陋是你需要使用mapiconcat再次压平序列。

This is not very nice, so a good thing to do would be to define your own higher-order function like Seq.takeUntilAfter that encapsulates the behavior you need (and hides all the ugly things). 这不是很好,所以做一件好事就是定义你自己的高阶函数,如Seq.takeUntilAfter ,它封装了你需要的行为(隐藏了所有丑陋的东西)。 Then your code could just use the function and look nice & readable (and you can experiment with other ways of implementing this). 然后你的代码可以只使用这个函数,看起来很好看和可读(你可以尝试其他实现方法)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM