4

When using union types with quite a few constructors I almost always find myself implementing lots of logic in single function, i.e. handling all cases in one function. Sometimes I would like to extract logic for single case to separate function, but one cannot have a function accepting only one "constructor" as parameter.

Example: Assume that we have typical "expression" type :

type Formula =    
| Operator of OperatorKind * Formula * Formula
| Number of double   
| Function of string * Formula list
[...]

Then, we would like to calculate expression :

let rec calculate efValues formula = 
match formula with
    | Number n -> [...] 
    | Operator (kind, lFormula, rFormula) -> [...]
    | [...]

Such function would be very long and growing with every new Formula constructor. How can I avoid that and clean up such code? Are long pattern matching constructs inevitable?

Artur Krajewski
  • 571
  • 1
  • 4
  • 18
  • Not sure what you mean by "extract logic for single case to separate function, but one cannot have a function accepting only one "constructor" as parameter.". Could you give an example in pseudo code? – Christian Jan 02 '15 at 20:51
  • Actually, yes, you can: `let test (Some(x)) = x` This gives a warning, but I think that is what you want right? – N_A Jan 02 '15 at 20:52
  • @mydogisbox: this will also give you a runtime exception when the pattern is not matched. I wouldn't use that for anything that has more than one case ;) – scrwtp Jan 02 '15 at 22:02
  • 2
    @scrwtp I'm not saying its a good idea, just that it was exactly what he was asking for: "Sometimes I would like to extract logic for single case". – N_A Jan 03 '15 at 01:42

2 Answers2

3

You can define the Operator case of the Formula union using an explicit tuple:

type Formula =    
 | Operator of (string * Formula * Formula)
 | Number of double   

If you do this, the compiler will let you pattern match using both Operator(name, left, right) and using a single argument Operator args, so you can write something like:

let evalOp (name, l, r) = 0.0
let eval f = 
  match f with
 | Number n -> 0.0
 | Operator args -> evalOp args

I would find this a bit confusing, so it might be better to be more explicit in the type definition and use a named tuple (which is equivalent to the above):

type OperatorInfo = string * Formula * Formula
and Formula =    
 | Operator of OperatorInfo
 | Number of double   

Or perhaps be even more explicit and use a record:

type OperatorInfo = 
 { Name : string
   Left : Formula 
   Right : Formula }
and Formula =    
 | Operator of OperatorInfo
 | Number of double   

Then you can pattern match using one of the following:

| Operator args -> (...)
| Operator { Name = n; Left = l; Right = r } -> (...)
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • I took the question to mean the number of cases in the DU, rather than the number of arguments for a given case constructor. I.e., that the`calculate` function would become too long as more cases are added. – ildjarn Jan 02 '15 at 21:46
  • @ildjarn I think he's saying that you can define a function for each case where the input is the contents of the case by using either explicit tuples or records. – N_A Jan 03 '15 at 04:14
2

I would say you typically want to handle all the cases in a single function. That's the main selling point of unions - they force you to handle all the cases in one way or another. That said, I can see where you're coming from.

If I had a big union and only cared about a single case, I would handle it like this, wrapping the result in an option:

 let doSomethingForOneCase (form: Formula) =
     match form with
     | Formula (op, l, r) -> 
         let result = (...)
         Some result
     | _ -> None

And then handle None in whatever way is appropriate at the call site.

Note that this is in line with the signature required by partial active patterns, so if you decide that you need to use this function as a case in another match expression, you can easily wrap it up in an active pattern to get the nice syntax.

scrwtp
  • 13,437
  • 2
  • 26
  • 30