F # stop Seq.map when the predicate evaluates to true

I am currently creating a sequence similar to the following:

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

The migrator function ultimately returns a discriminatory union, for example:

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

I want to stop map after it encountered my first Error , but I need to include Error in the final sequence.

I have something like the following to display the last 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" 

I need:

  • A way to stop the display when one of the map steps creates an Error
  • The way to get this error is a finite element added to the sequence

I understand that there may be a different sequence method, other than map , that will do this, I am new to F # and searching the Internet has not brought anything yet!

+6
source share
3 answers

I assume there are several approaches here, but one way is to deploy:

 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 

Check out List.ofSeq at the end, which is just for implementing the sequence. Another way to go is to use consistent understandings, some may say that this leads to clearer code.

+6
source

The ugly things that Tomas is hinting at are 1) a volatile state and 2) manipulating a basic enumerator. A higher-order function that returns before and when the predicate is held will 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 map DU cases to logical.

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

I think the answer from @scrwtp is probably the nicest way to do this if your input is small enough (and you can turn it into an F # list to use pattern matching). I will add another version that works when your input is a sequence and you do not want to turn it into a list.

Essentially, you want to do something similar to Seq.takeWhile , but it gives you one additional element at the end (one for which the predicate does not work).

To use a simpler example, the following returns all numbers from a sequence up to those divisible by 5:

 let nums = [ 2 .. 10 ] nums |> Seq.map (fun m -> m % 5) |> Seq.takeWhile (fun n -> n <> 0) 

So, in principle, you just need to look for one element forward - for this you can use Seq.pairwise , which gives you the current and next element in the sequence "

 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 ugliness is that you need to smooth out the sequence again using mapi and concat .

This is not very nice, so it would be nice to define your own function of a higher order, for example, Seq.takeUntilAfter , which encapsulates the behavior you need (and hides all ugly things). Then your code could just use the function and look nice and readable (and you can experiment with other ways to implement this).

+2
source

Source: https://habr.com/ru/post/978092/


All Articles