Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pattern match on union constructors in `for .. in`

In Haskell, if I have a list of union typed values like this:

example :: [Either Int Char]
example = [Left 3, Right 'b', Left 6, Left 9, Right 'c']

I can use a little "trick" to extract all the results matching some specific pattern:

lefts :: [Int]
lefts = [l | Left l <- example]

However, if I try to translate this to F#, I get an error:

let lefts = [for Choice1Of2 l in example -> l]
                 ~~~~~~~~~~~~
Incomplete pattern matches on this expression. (...)

This makes a lot of sense (it might even be better behavior than silently ignoring Right values like Haskell does!), but in F#, is there some convenient way to extract (and match on) all values matching a certain pattern in a list/sequence?

like image 278
Lynn Avatar asked Oct 05 '15 20:10

Lynn


2 Answers

In F# if you don't match against all cases you will get a warn, in all scenarios.

So you can write the match with both cases inside the expression, but for your example rather than comprehensions I would use the function List.choose:

let example = [Choice2Of2 3; Choice1Of2 'b'; Choice2Of2 6; Choice2Of2 9; Choice1Of2 'c']
List.choose (function (Choice1Of2 x) -> Some x | _ -> None) example
// val it : char list = ['b'; 'c']

This function is handy for those cases.

like image 172
Gus Avatar answered Sep 28 '22 18:09

Gus


I think the closest thing you can do using F# list expressions is something like this:

let lefts example = 
  [ for e in example do
      match e with Choice1Of2 l -> yield l | _ -> () ]

If I understand the Haskell code correctly, the part after | is used not just as an extractor, but also as a filter - implicitly skipping over all things that do not match the pattern.

F# does not have the same kind of concept in list expressions, so you have to be more verbose. Here, we just iterate over all items using for and then we explicitly use yield to produce a new value for each Choice1Of2 in the source list (and we just skip over anything else).

Depending on what you're doing, using List.choose (as mentioned in Gustavo's answer) might be easier. But the above is probably closest you can get to the Haskell's comprehension syntax.

like image 24
Tomas Petricek Avatar answered Sep 28 '22 16:09

Tomas Petricek