Search code examples
dictionaryf#seq

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:

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.

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
  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!


Solution

  • 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. A different way to go would be to use sequence comprehensions, some might say it results in a clearer code.