Friday, February 20, 2009

[2009.02.20] F# Types: Discriminated Unions [III/III]

When I first encountered this type, I was confused to say the least. One reason for my confusion was that I found several names for this one concept. So when you see union, discriminated union, variant type or sum type, know they are all referring to the same thing. One powerful feature of this type is that it is recursive, in that it can refer to itself. This type finds the perfect niche in the area of Domain Specific Language (DSL) or alternatively called Language Oriented Programming. I will not go into DSLs here but you can think of a DSL as a mini language within your programming language that you create to solve a specific kind of problem. For example, you can create a DSL to actually create your own version of a XSLT, that is, a DSL to transform XML to some other variant, like XHTML.

A union is defined again with the type keyword followed by an equals sign and the list separated by the vertical pipe, |. Each "branch" or constructor in the union is called a discriminator. In order to use the type, you use the discriminator to build values and basically treat it as a function. If the discriminator is composed of multiple types, these are separated by asterisks, *.

    1 #light

    2 #r "FSharp.PowerPack.dll"

    3 

    4 // create some types to use

    5 type Real = float

    6 type Imag = float * string

    7 

    8 // build a union based on types above

    9 type Numbers =

   10     | Complex of Real * Imag

   11     | Natural of Real

   12 

   13 // use each discriminator like a function

   14 Complex(1.,(1.,"j"))

   15 // the signature of this is:

   16 > val it : Numbers =

   17     Complex (1.0,(1.0, "j"))

   18 

   19 Natural(1.)

   20 // the signature of this is:

   21 > val it : Numbers = Natural 1.0

Pattern matching can be used to decompose a union type. Each rule is made up of the constructor/ discriminator in the first part, followed by identifiers which will match the different values in that particular discriminator. Instead of using identifiers, you can also use the wildcard, which in F# is the underscore, _ , to match all values in the constructor.

    1 #light

    2 #r "FSharp.PowerPack.dll"

    3 

    4 // create some types to use

    5 type Real = float

    6 type Imag = float * string

    7 

    8 // build a union based on types above

    9 type Numbers =

   10     | Complex of Real * Imag

   11     | Natural of Real

   12 

   13 let myNumber (num: Numbers) =

   14     match num with

   15     | Complex _  -> "complex"

   16     | Natural _ -> "natural"

   17 

   18 // can be used like

   19 myNumber (Complex(0., (0., "j")))

   20 > val it : string = "complex"

   21 

   22 myNumber (Natural(0.))

   23 > val it : string = "natural"

While I am no expert on DSLs or even unions, I am learning as much as I can to understand these powerful features of F#.

No comments: