Unit testing with types

I’m an incredibly sloppy programmer, and I lean on the compiler heavily to help me not be stupid. One of my favourite things is to use types to enforce properties of data.

The idea is to create a type that can only be constructed with a “smart” constructor that checks for validity on creation. In code:

module GreenThing (GreenThing, makeGreenThing) where

newtype GreenThing = GreenThing String deriving (Show, Eq)

makeGreenThing :: String -> Maybe GreenThing
makeGreenThing s = case s of
  "green" -> Just (GreenThing "green")
  "lime -> Just (GreenThing "lime")
  "moss -> Just (GreenThing "moss")
   _ -> Nothing

We’re not exporting the constructor GreenThing, only the type GreenThing. To get a value of type GreenThing I need to go through makeGreenThing which makes it impossible to create an invalid value.

With this setup I know that GreenThing contains something green, no matter where I use it. This frees my mind for other tasks.

I use this in place of unit tests: Every time I have a new entity I create a type with a smart constructor, test it a bit in the repl, and then use it without ever writing a single test. It works surprisingly well!