Using template haskell to generate domain types

It’s advisable to use newtype and create new types, instead of using basic types over and over again. This lets you tell apart FirstName, LastName and FavouriteMusic.

There are several instances that one ends up writing often when using newtypes: ToJSON, FromJSON, PersistField, PersistFieldSql and IsString. Some template haskell can save a lot of repetitive writing here. There’s an initial implementation in legislation branch. Eventually it’ll be in the master branch too, when other things in legislation branch are ready.

Module Templates has a single exported function, makeDomainType, which is used to create a newtype definition and instances mentioned before. Invoke it as any template haskell: $(makeDomainType "PlanetName" ''Text). First parameter is name of the new type to create and the second one is the type of the wrapped value. Currently only Int and Text are supported. This will create following code:

newtype PlanetName
    = MkPlanetName {_unPlanetName :: Text}
    deriving (Show, Read, Eq)

instance IsString PlanetName where
    fromString = (MkPlanetName . fromString)

instance ToJSON PlanetName where
    toJSON = (toJSON . _unPlanetName)

instance FromJSON PlanetName where
    parseJSON = (withText "PlanetName") (return . MkPlanetName)

instance PersistField PlanetName where
    toPersistValue (MkPlanetName s) = PersistText s
    fromPersistValue (PersistText s) = (Right $ MkPlanetName s)
    fromPersistValue _ = Left "Failed to deserialize"

instance PersistFieldSql PlanetName where
    sqlType _ = SqlString

In case of Int, IsString instance will not be generated.

If there are errors in generated code, you can dump the crated code in file for further examination, by adding --ddump-splices --ddump-to-file to ghc options. I often run tests with command stack test --flag sky:dev --flag sky:library-only --ghc-options "--ddump-splices --ddump-to-file". Dumped files can be found under Cabal work directory: .stack-work/dist/<architecture>/build/src.