3
votes

I am trying to understand the following code, particularly StringConstant:

type StringConstant = StringConstant of string * string 

[<EntryPoint>]
let main argv = 
    let x = StringConstant("little", "shack")
    printfn "%A" x

    0 // return an integer exit code

(By way of context, StringConstant is used in the FParsec tutorial, but this example does not use FParsec.)

What I would like to know is:

  1. what exactly is the type statement doing?

  2. once I instantiate x, how would I access the individual "parts" ("little" or "house")

4

4 Answers

7
votes

As others already noted, technically, StringConstant is a discriminated union with just a single case and you can extract the value using pattern matching.

When talking about domain modelling in F#, I like to use another useful analogy. Often, you can start just by saying that some data type is a tuple:

type Person = string * int

This is really easy way to represent data, but the problem is that when you write "Tomas", 42, the compiler does not know that you mean Person, but instead understands it as string * int tuple. One-case discriminated unions are a really nice way to name your tuple:

type Person = Person of string * int

It might be a bit confusing that this is using the name Person twice - first as a type name and second as a name of the case. This has no special meaning - it simply means that the type will have the same name as the case.

Now you can write Person("Tomas", 42) to create a value and it will have a type Person. You can decompose it using match or let, but you can also easily write functions that take Person. For example, to return name, you can write:

let getName (Person(name, _)) =  
  name

I think single-case discriminated unions are often used mainly because they are really easy to define and really easy to work with. However, I would not use them in code that is exposed as a public API because they are a bit unusual and may be confusing.

PS: Also note that you need to use parentheses when extracting the values:

// Correct. Defines symbols 'name' and 'age'
let (Person(name, age)) = tomas 

// Incorrect! Defines a function `Person` that takes a tuple 
// (and hides the `Person` case of the discriminated union)
let Person(name, age) = tomas
2
votes

StringConstant is a discriminated union type, with just a single case (also named StringConstant). You extract the parts via pattern matching, using match/function or even just let, since there is just a single case:

let (StringConstant(firstPart, secondPart)) = x
1
votes
type StringConstant = StringConstant of string * string 

results in a discriminated union with one type.

type StringConstant = | StringConstant of string * string if you execute it in F# interactive.

You can see the msdn documentation on that here.

You can get the value out like this:

let printValue opt =
    match opt with
    | StringConstant( x, y) -> printfn "%A%A" x y
1
votes

The other guys already mentioned how you extract the data from a discriminated union, but to elaborate a little more on Discriminated unions one could say that they are sorta like enums on steroids. They are implemented behind the scenes as a type hierarchy where the type is the base class and the cases are subclases of that baseclass with whatever parameter they might have as readonly public variables.

In Scala a similar data-structure is called case classes which might help you convince yourself of this implementationmethod.

One nice property of discriminated unions are that they are self-referenceable and therefor are perfect for defining recursive structures like a tree. Below is a definition of a Hoffman coding tree in just three lines of code. Doing that in C# would probably take somewhere between 5 and 10 times as many lines of code.

type CodeTree =
     | Branch of CodeTree * CodeTree * list<char> * int
     | Leaf of char * int

For information about Discriminated Unions see the msdn documentation

For an example of using Discriminated Unions as a tree-structure see this gist which is an implementation of a huffman decoder in roughly 60 lines of F#)