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!