Source code for this blog post can be found here.
One of my favorite ways of learning a programming language is to just create little projects for myself that I want to learn. The process of doing the research to learn how to achieve a particular programming task I often find more enjoyable than trying to understand all of the in’s and out’s of a language. That said, one project I embarked on to get better at Haskell was reading a yaml config file. Pretty much all programming projects I have worked on have had some sort of configuration so I felt that this little project would be well worth the effort.
Libraries
As always, the first place we start looking is for libraries. Out of
the gate we find the yaml
and yaml-config
libraries. The more general yaml
library seems to specifically deal
with yaml data. On the otherhand, the yaml-config
library seems to
go a step further and deals with loading a config file from
disk. Looking at it’s .cabal
file it seems that unsurprisingly
yaml-config
uses the yaml
library behind the scenes. I always like
to work at the highest level of abstraction if possible and it seems
that yaml-config
may do exactly what we are looking for so lets
start there.
yaml-config
We’ll start off by creating a basic project with stack:
stack new readyamlconfig
stack setup
cd readyamlconfig
stack build
:~/shared/haskell/readyamlconfig$ stack exec readyamlconfig-exe
someFunc
We can see that executing the basic stub program with stack exec
readyamlconfig-exe
outputs someFunc
. This means our scaffolded
program is ready for us to write some code.
The next thing we’ll want to do is go ahead and add the yaml-config
library dependency to our readyamlconfig.cabal
and stack.yaml
files.
readyamlconfig.cabal
library
hs-source-dirs: src
exposed-modules: Lib
build-depends: base >= 4.7 && < 5
, yaml-config
default-language: Haskell2010
executable readyamlconfig-exe
hs-source-dirs: app
main-is: Main.hs
ghc-options: -threaded -rtsopts -with-rtsopts=-N
build-depends: base
, readyamlconfig
, yaml-config
default-language: Haskell2010
stack.yaml
extra-deps: [yaml-config-0.4.0]
Then we rebuild the project with stack build
to acquire the dependencies.
Next we create a basic yaml
config file:
config.yaml
config:
items:
- thing one
- thing two
Now lets write some code:
{-# LANGUAGE OverloadedStrings #-}
module Lib
( someFunc
) where
import Data.Yaml.Config (load, subconfig, lookupDefault)
someFunc :: IO ()
someFunc = do
configFile <- load "./config.yaml"
cfg <- subconfig "config" configFile
let items = lookupDefault "items" [] cfg
mapM_ putStrLn items
This library seems to have a dependency on OverloadedStrings so I have
added that from the example on the github page. The code is all pretty
straight forward. We load up the file, grab the “config” key and get
the collection of “items”. lookupDefault
is used here as an example
of how a default can be specified if a key is not found in a config
file.