Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Moving from static configuration to dynamic configuration

I am working on a haskell project where the settings are currently in a file called Setting.hs, so they are checked during compile time and can be accessed globally.

However, since that is a bit too static, I was considering to read the configuration during runtime. The codebase is huge and it seems it would be considerable effort to pass the setting e.g. as an argument through the whole program flow, since they may be arbitrarily accessed from anywhere.

Are there any design patterns, libraries or even ghc extensions that can help here without refactoring the whole code?

like image 311
hasufell Avatar asked May 09 '15 12:05

hasufell


1 Answers

Thanks for the hints! I came up with a minimal example which shows how I will go about it with the reflection package:

{-# LANGUAGE Rank2Types, FlexibleContexts, UndecidableInstances #-}

import Data.Reflection

data GlobalConfig = MkGlobalConfig {
    getVal1 :: Int
  , getVal2 :: Double
  , getVal3 :: String
}

main :: IO ()
main = do
  let config = MkGlobalConfig 1 2.0 "test"
  -- initialize the program flow via 'give'
  print $ give config (doSomething 2)
  -- this works too, the type is properly inferred
  print $ give config (3 + 3)
  -- and this as well
  print $ give config (addInt 7 3)

-- We need the Given constraint, because we call 'somethingElse', which finally
-- calls 'given' to retrieve the configuration. So it has to be propagated up
-- the program flow.
doSomething :: (Given GlobalConfig) => Int -> Int
doSomething = somethingElse "abc"

-- since we call 'given' inside the function to retrieve the configuration,
-- we need the Given constraint
somethingElse :: (Given GlobalConfig) => String -> Int -> Int
somethingElse str x
  | str == "something"      = x + getVal1 given
  | getVal3 given == "test" = 0 + getVal1 given
  | otherwise               = round (fromIntegral x * getVal2 given)

-- no need for Given constraint here, since this does not use 'given'
-- or any other functions that would
addInt :: Int -> Int -> Int
addInt = (+)

The Given class is a bit easier to work with and perfectly suitable for a global configuration model. All functions that do not make use of given (which gets the value) don't seem to need the class constraint. That means I only have to change functions that actually access the global configuration.

That's what I was looking for.

like image 116
hasufell Avatar answered Sep 19 '22 07:09

hasufell