8

Let's say you have this union:

type Thing = 
| Eagle
| Elephant of int

And your code has a list of Elephants, as in

let l = [Elephant (1000); Elephant (1200)]

And you wanted to iterate over l, and print out the data associated with each Elephant. Is there a way to do so without using a pattern match?

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
Michael Ray Lovett
  • 6,668
  • 7
  • 27
  • 36

5 Answers5

11

In your example, you say that you have a list of elephants - which is true in this case - but the type of l is really a list of Thing values and so it can contain both elephants and eagles. This is why you need to use pattern matching - to handle all possible cases.

If you regularly need to use list that contain only elephants, then it might make sense to define a separate type of elephants. Something like:

type ElephantInfo = { Size : int }

type Thing = 
  | Elephant of ElephantInfo
  | Eagle

Now you can create a list of type list<ElephantInfo> which can contain just elephants and so you don't need pattern matching:

let l1 = [ {Size=1}; {Size=2} ]
for el in l1 do printfn "%d" el.Size

On the other hand, if you want to mix elephants and eagles, you'll create list<Thing> and then use pattern matching:

let l2 = [ Elephant {Size=1}; Eagle ]
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
4

You could do this:

l
|> List.collect (function Elephant x -> [x] | _ -> [])
|> List.iter (printfn "%i")

Prints

1000
1200

It still uses pattern matching, but it's fairly minimal.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • 1
    `List.choose (function Elephant x -> Some x | _ -> None)` looks better here. – Be Brave Be Like Ukraine May 28 '15 at 03:53
  • Thanks, this is effectively what I am doing now. – Michael Ray Lovett May 28 '15 at 15:21
  • Maybe I'm not using DU's well. I'm using them to internally represent command line options the user has entered, and one of the cases is "InvalidCommand as string", to represent a command the system doesn't recognize. A bit later in the code, I extract out all InvalidCommands from this list so I can handle them as a separate use case. The code that handles those invlalid commands needs to do a pattern match to get the string data (the incorrect text the user entered at the command line) and it can't just treat the list as only having InvalidCommands, it has to account for all cases of the DU – Michael Ray Lovett May 28 '15 at 15:26
  • @MichaelRayLovett FWIW, here's how we parse command-line arguments with DUs in Zero29: https://github.com/ploeh/ZeroToNine/blob/master/Src/Zero29/Args.fs#L26-L44 – Mark Seemann May 28 '15 at 17:49
  • *Very* nice idea of using `collect` in this case. – Nikos Baxevanis Jun 01 '15 at 08:17
3

You have of course the option of going full Ivory Tower (® Scott Wlaschin)

As in about:

type Thing = 
 | Eagle
 | Elephant of int

type MaybeElephantBuilder() =      
 member this.Bind(x, f) = 
  match x with
  | Eagle -> 0
  | Elephant a -> f a

 member this.Return(x) = x

let maybeElephant = new MaybeElephantBuilder()

let l = 
 [ Elephant(1000)
   Elephant(1200)
    ]

let printIt v = 
 let i = 
  maybeElephant { 
   let! elephantValue = v  
   return elephantValue
  }
 printfn "%d" i   

l |> Seq.iter printIt

It will even handle the stuff with the Eagles thrown in there!

Well...

Remove the non-Eagles and the code will fly...

let l = 
 [ Eagle
   Leadon
   Elephant(1000)
   Eagle
   Meisner
   Elephant(1200)
   Eagle
   Felder
    ]

l |> Seq.iter printIt

But no. Its not nice. Its not short. Its more for fun (if that!) than anything else. Its probably the worst misuse of F# computation expressions ever too!

And you will need pattern matching somewhere.

Thx Scott! And Petricek.

Computation Expression Zoo for real! ;-)

Helge Rene Urholm
  • 1,190
  • 6
  • 16
  • 1
    NIce first post. Looking forward to similar stuff over the coming years! – Ruben Bartelink May 27 '15 at 22:54
  • 1
    @RubenBartelink Nah, not really. Not first anyway ;-) – Helge Rene Urholm May 28 '15 at 07:02
  • @user1758475 Ah, but can you *prove* you're user4946443 :P Cmon, there must be internet handle generator apps out there! – Ruben Bartelink May 28 '15 at 11:45
  • 1
    @RubenBartelink the simple explanation is that the computer I was at yesterday evening was unable to log in to this account, which this computer does. and I wanted this posted. and yes I have looked into how to merge these "accounts"... not there yet though. so for now consider the two different accounts as "daytime-me" and "evening-me" ;-) – Helge Rene Urholm May 28 '15 at 12:32
  • @RubenBartelink and now it is kind of proved that it is same user ;-) Finally got it merged with some help from the good folks at SO... – Helge Rene Urholm Sep 02 '16 at 11:14
1

You can use reflection from Microsoft.FSharp.Reflection Namespace but it is much more cumbersome and slow.

Pattern matching is probably the easiest way to get data from discriminated union.

(Also you have a list of Things all its members happen to be of Elephant union case).

Petr
  • 4,280
  • 1
  • 19
  • 15
1

There's a way to place the pattern match into the header of the function (or a let binding). It is still a pattern match, though.

// This function takes a tuple:
// the first argument is a Thing,
// the second is "default" weight to be processed if the first one is NOT an Elephant
let processElephant (Elephant weight, _ | _, weight) =
    weight

let [<Literal>] NON_ELEPHANT_WEIGHT = -1

// usage:
let totalWeight =
    [Elephant (1000); Elephant (1200)]
    |> List.sumBy (fun el -> processElephant(el, NON_ELEPHANT_WEIGHT))

This question and its answers provide with more details.

Community
  • 1
  • 1
Be Brave Be Like Ukraine
  • 7,596
  • 3
  • 42
  • 66