F#-intro in 3 minutes: Playing cards and discriminated union types
Tags: fsharp
Published: 2020-02-13 14:58:18

---
title: F#-intro in 3 minutes: Playing cards and discriminated union types
published: true
description: A short code to illustrate why I love F#
tags: fsharp
---

I have written a short code to tell someone why I love F#. Describes some of the language features by writing a part of card game program as an example.

## [1/4] Exhaustive pattern matching

I define `Suit` as a discriminated union type. Same as enum types in C# here.

```fsharp
type Suit =
    | Spade
    | Clover
    | Heart
    | Diamond
```

For instance, I define a function to get the name of suits.

```fsharp
let suitToName suit =
    match suit with
    | Spade     -> "Spade"
    | Clover    -> "Clover"
    | Heart     -> "Heart"
    | Diamond   -> "Diamond"
```

`match...with` is a syntax for branching like `switch` in C#. Unlike `switch` statements, a `match` expression gives a compile-time warning for you unless the patterns cover all cases.

The function works like this.

```fsharp
printfn "%s" (suitToName Spade) //=> "Spade"
```

And then let's see a compile-time warning. Comment the `Diamond` case out.

```fsharp
let suitToName suit =
    match suit with
    | Spade     -> "Spade"
    | Clover    -> "Clover"
    | Heart     -> "Heart"
    // | Diamond   -> "Diamond"
```

The compiler now emits a warning in a console or on an editor.

```fsharp
warning FS0025:
    Incomplete pattern matches on this expression.
    For example, the value 'Diamond' may indicate a case not covered by the pattern(s).
```

## [2/4] Discriminated unions with fields

I also define `Card` as a discriminated union type. Each case of discriminated union types can have fields.

```fsharp
type Card =
    | NormalCard of suit:Suit * rank:int
    | Joker
```

Field definitions follow `of` and are separated by `*`s. 

You can tell that the code above almost literally describes the following facts.

- A card is either a normal card or Joker.
- A normal card has a suit and a rank.
- Joker doesn't have any property such as suit or rank.

A case with fields can new up with a *constructor*.

```fsharp
let heart3 = NormalCard (Heart, 3)
```

For instance, I define a function to get the name of cards. Same as the above example, make a branch and implement each case.

```fsharp
let cardToName card =
    match card with
    | Joker ->
        "Joker"

    | NormalCard (suit, rank) ->
        let suitName = suitToName suit
        let rankName = string rank
        rankName + " of " + suitName
```

Output examples:

```fsharp
printfn "%s" (cardToName Joker) //=> Joker
printfn "%s" (cardToName heart3) //=> 3 of Heart
```

There are a large number of good situations where discriminated union types work fine.

```fsharp
type HttpResponse =
    | OkWithText    of text:string
    | OkWithJson    of json:obj
    | Redirect      of uri:string * temporary:bool
    | InternalError of ex:exn

type BinaryTree<'T> =
    | Node of left:BinaryTree<'T> * right:BinaryTree<'T>
    | Leaf of value:'T

type Contact =
    | ContactWithMail
        of address:string

    | ContactWithDelivery
        of zipCode:string * address:string * recipient:string
```

## [3/4] Type inference

By the way, the functions above compile even if **I didn't write any parameter/result types explicitly**. However, F# isn't a dynamic language -- type errors are reported at compile-time if any.

```fsharp
printfn "%s" (cardToName "Joker")
//                       ^^^^^^^ the constant Joker is correct here
```

Note that public APIs should be explicitly typed for readability, though.

## [4/4] Wrapping up

In a nutshell, **brevity** is why I love F#. Could you image how much codes you need to write to define discriminated union types by using abstract classes and inheritance?

Of cause that's not all of the reason... however, it's time to end. I'm glad if you get interested in F#.

## Appendix

On `try.fsharp.org`, you can play with F# in the browser. [This is the full code of the article.](https://try.fsharp.org/#?code=LYewJgrgNgpgBABQE4gOZIIbAFDYC4CeADvAMoQCWecAvNnA3AD5ylEZgz2MsDCUIAG4wk3BiwASMDEjxjmcACIUsIAHZhcsagGdKeACogAclnh6qtecAx4AxgAs4F6gHcqD+SzYd4jOAC0AHxwAEQ+nKFecPxCIv7BYbHCSFH+ktKy-oEhoVIyeGk8SiqgGoyJocqqGlHYREgUangAZmphETAAXHAApDqhcAAULkamwGTsnACUcAD0czQhnbiEJDEyYFbpcMYgSDZQvJtwIC3O+l3klgBUcJhqANZdTXI7AFIgjyJaMNQOmTwAGZaLt9odjkgtkN8rIADRwIHTX7UOybMZmOBoqHbRg2exObFbdx4TwfL7xYLyfyhT7fVK4HZ7A4YI4nEb6BEPR6zKnZRjaC5UcbwGhCwwmTEuakCv73DBPEWgnR4RpqVDyp4yhjcpUAajCp3OgwNLhFuAarzaYTpIh6-UGQyJGImcFtSFmCyWbopoktzWtoQBBSB9oGw2dktdwdkSPmixCILOcFhcmwCzgAFU1HYQMAJs04C19li80QKLAAngKK7XDI1E1UF103M4P7Wu1Qg6I+io-Bab7QsiW4EAmPxxPJ1PpzPZ3P5wER7wHKzYOquBmF1vtzvt7gM7wQNAtgQj0WKAAPOCk+C5zhwDAAIzi15A56vN7gakxyYwdhgOgAPwjn+eAQKyPQAIxGimgIjjAF4kHYeAwGAPR-vAyapvUaodmEqYAIJho6kZKkMzIQuyqYIpB0x0furYSK8zaCjoGAEJqjy4gw+KOJxcAkmSxTQZUACSADkwAPu0GEAHRFOIcAAPo5GEElScYADyBjSQ+-6yeJURAA&html=DwCwLgtgNgfAsAKAAQqaApgQwCb2ag4CdMTJcMABwFp0BHAVwEsA3AXgCIBhAewDsw6AdQAqAT0roOSAMb9BAzoIAeYAPThoAbhkhMAJwDOJNgzAAzagA4OeQhqy5EhAEY9sYu6mBq3HvD6asEA&css=Q)

And I made a quiz. Give it a try!

```fsharp
// Could you fix the code above to fix the name of aces?
// actual: 1 of Heart
// expected: ace of Heart
printfn "HeartA: %s" (cardToName (NormalCard (Heart, 1)))

// Hint: instead of the `string` function...
let rankToName rank =
    match rank with
    | 1 -> "I'm an ace."
    | _ -> "I'm NOT an ace.'"
```

P.S. This is a translation of [my article](https://qiita.com/vain0x/items/3588b762ebb15835b8c2) written in Japanese published on 2020-02-09.