Skip to content

Instantly share code, notes, and snippets.

@saurabhnanda
Last active March 25, 2022 06:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save saurabhnanda/a3a4aa672736a5348915c1ae1881153d to your computer and use it in GitHub Desktop.
Save saurabhnanda/a3a4aa672736a5348915c1ae1881153d to your computer and use it in GitHub Desktop.
Comparitive code samples between Dhall and Haskell . Complete blog post at https://www.saurabhnanda.in/2022/03/24/dhall-a-gateway-drug-to-haskell/
-- Right at the very beginning, you have to deal with the mess of string types in Haskell.
{-# LANGUAGE OverloadedStrings #-}
module Config where
import Data.Aeson as Aeson
-- Union/sum type to decide the logging configuration
data LoggingCfg = LogFile String | Syslog
-- Next you have to deal with the fact that having records share the
-- same field name doesn't work very well in Haskell - to this date.
-- Therefore you have to be careful to prefix each field name such that
-- the field name becomes unique.
data CommonCfg = CommonCfg
{ cfgLogging :: LoggingCfg
, cfgTopic :: String
}
-- Config values for development environment
devCfg :: CommonCfg
devCfg = CommonCfg
{ cfgLogging = LogFile "log/dev.log"
, cfgTopic = "devTopic"
}
-- Config values for production environment. This is not really being
-- used in this code sinppet, and is just an example to show how duplication
-- across environments can be managed.
prodCfg :: CommonCfg
prodCfg = CommonCfg
{ cfgLogging = Syslog
, cfgTopic = "prodTopic"
}
-- Sadly, Haskell does not support multi-line string literals out of the box.
-- You have to use a package like `here` along with TemplateHaskell or
-- QuasiQuotes extension to get this basic feature.
--
-- Apart from this quirk, conceptually the handling of a sum-type is the same as
-- what is happening in the Dhall code snippet.
appOneCfg :: CommonCfg -> String
appOneCfg cfg =
let loggingProps = case cfgLogging cfg of
LogFile f -> "logdriver = file\n" <>
"logfile = " <> f <> "\n"
Syslog -> "logdriver = syslog\n"
in "topic = " <> (cfgTopic cfg) <> "\n" <>
loggingProps
-- Typically, this is NOT recommended in real-life code. You would probably
-- auto-derive the Aeson.ToJSON type-class to do the Config -> JSON conversion
-- for you automagically. But that would force you to first understand type-classes,
-- generics, and auto-deriving type-classes. Thus contributing to the steep learning
-- curve of Haskell for something as basic as emitting well-formed JSON.
--
-- Manually generating the JSON AST has been done to stay away from all this complexity
-- for the purpose of ths code snippet.
--
-- Compare this to Dhall's approach, which can seamlessly convert ANY Dhall record
-- into a well-formed JSON via the `dhall-to-json` utility. Even though one can
-- write similar code in Dhall to manually generate the JSON AST, one wouldn't be forced
-- to do that apart from very specific situations.
appTwoCfg :: CommonCfg -> Aeson.Value
appTwoCfg cfg =
let loggingJson = case cfgLogging cfg of
LogFile f -> Aeson.object [ "driver" .= (Aeson.String "file")
, "file" .= f
]
Syslog -> Aeson.object [ "driver" .= (Aeson.String "syslog") ]
in Aeson.object
[ "topic" .= (cfgTopic cfg)
, "logging" .= loggingJson
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment