| Safe Haskell | Safe |
|---|---|
| Language | Haskell2010 |
HKD.Default
Description
When I use Data.Aeson library to decode a json string into a Haskell value,
I want to provide default values for Maybe fields, which will be Nothing
when these fields are omitted in the json string. It's a hard work when there
are lots of Maybe fields or deeply nested fields, it will also make your code
hard to read and to maintain. This module provides a solution by using
Higher-kinded data (HKD).
See this blog
for more information about HKD.
For example, if you have a Config type as follows,
data Config = Config { dbHost :: String
, dbPort :: Int
, dbName :: String
...
}
and you want to read these configuration data from a json file when you start
you application, you instantiate Data.Aeson.FromJSON for the Config,
data Config = Config { dbHost :: String
, dbPort :: Int
, dbName :: String
...
} deriving Generic
instance FromJSON Config
and you want dbPort can be omitted in the json string, a default value will be
used when it is omitted, you change String type to Maybe String,
data Config = Config { dbHost :: String
, dbPort :: Maybe Int
, dbName :: String
...
} deriving Generic
instance FromJSON Config
and decode and use the configuration data in main function as below,
main = do
config <- fromJust <$> decodeFileStrict "./config.json"
let host = dbHost config
port = fromMaybe defaultDBPort $ dbPort config
...
dbConn <- connectDB host port ...
...
it is neither elegant nor easy to maintain when you have lots of configuration items.
By using HKD and type family, it becomes easier to maintain your code.
type family HKD f a where
HKD Identity a = a
HKD f a = f a
data ConfigH f = Config { dbHost = String
, dbPort = HKD f String
...
} deriving Generic
instance Default ConfigH
instance FromJSON (ConfigH Maybe)
type Config = Config Identity
instance FromJSON Config where
parseJSON v = applyDef def $ parseJSON v
where
def = Config undefined 3306 ...
main = do
-- Enable RecordWildCards extension
Config{..} <- fromJust <$> decodeFileStrict "./config.json"
dbConn <- connectDB dbHost dbPort ...
More Examples
>>>:set -XDeriveGeneric>>>:set -XFlexibleInstances>>>:set -XStandaloneDeriving>>>import Data.Functor.Identity>>>import GHC.Generics>>>import HKD.Default>>>:{data Triple f = Triple String (f Int) (f Double) deriving Generic instance Default Triple deriving instance Show (Triple Identity) :} ...>>>let def = Triple "hello" (Identity 123) pi :: Triple Identity>>>applyDef def $ Triple "world" (Just 456) NothingTriple "world" (Identity 456) (Identity 3.141592653589793)
>>>:set -XDeriveGeneric>>>:set -XStandaloneDeriving>>>:set -XFlexibleInstances>>>:set -XOverloadedStrings>>>import Data.Aeson>>>import Data.Functor.Identity>>>import GHC.Generics>>>import HKD.Default>>>:{data Name f = Name { first :: f String , last_ :: f String } deriving Generic instance Default Name deriving instance Show (Name Identity) instance FromJSON (Name Maybe) data Person f = Person { name :: Name f -- name is required , age :: f Int -- age is optional (can be omitted) } deriving Generic instance Default Person deriving instance Show (Person Identity) instance FromJSON (Person Maybe) instance FromJSON (Person Identity) where parseJSON v = applyDef def <$> parseJSON v where def = Person (Name (Identity "Jorah") (Identity "Gao")) (Identity 28) :}>>>decode "{\"name\": {}}" :: Maybe (Person Identity)Just (Person {name = Name {first = Identity "Jorah", last_ = Identity "Gao"}, age = Identity 28})>>>decode "{}" :: Maybe (Person Identity)Nothing