Last active
March 25, 2022 06:36
-
-
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/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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