Cardinality

(hopefully) explained

What is cardinality?


type Bool = True | False

type Foo = One Bool | Two Bool | Three Bool 

type Bar = (Bool, Bool, Bool)

type Baz = Baz 
   { a : Bool 
   , b : Bool 
   , c : Bool 
   }

Combinations


type Thingy = Foo | Bar | Baz

type Whatsit = (Foo, Bar, Baz)

type Widget = Foo | (Bar, Baz)

Why do we care?


head : List a -> a

head : List a -> Maybe a

head : NonEmptyList a -> a

This is everywhere


divide : Float -> Float -> Float        -- ☠️

type Person = Person 
    { name : String -- ☠️
    , age : Int -- ☠️
    , gender : String -- ☠️
    , postcode : String -- ☠️☠️☠️
    }

type Data = Data 
    { loading : Bool
    , error : Bool
    , stuff : Maybe Stuff
    }

Alternatives?


divide : Float -> NonZeroFloat -> Float        -- ☠️
type Gender = Gender String 
makeGender : String -> Maybe Gender

type Person = Person 
    { name : String -- ☠️
    , gender : Gender
    }
type Data e a
    = Loading 
    | Error e
    | Success a

How do we fix this?

Create the right types

Instantiate them at the right time

Parse. Don't validate.

Show me a swagger schema and I will show you a type with the wrong cardinality

Things to watch out for

  • Ad hoc validation (aka shotgun parsing)
  • if without else
  • Automatic deserialisation
  • Mutable types / setters
  • Public / multiple constructors
  • Void validation functions
  • Product types with nullable properties (they are sum types in disguise)
  • Nullable types

Valid values

Possible values

Input

Good modelling

* the red is "stuff you shouldn't have to deal with"

Valid values

Questions

Made with Slides.com