6

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?

hasufell
  • 533
  • 3
  • 9
  • Implicit arguments or reader monad are common choices, but they do require some change. – chi May 09 '15 at 13:48
  • 1
    Have a look at [Implicit configurations -- or, type classes reflect the values of types](http://okmij.org/ftp/Haskell/types.html#Prepose), if it could help. – Petr May 09 '15 at 14:42
  • 2
    Expanding on @Petr Pudlák comment, an implementation of implicit configurations can be found in the `reflection` package. In the examples folder from the repo there's a "reader-like" example that seems relevant: https://github.com/ekmett/reflection/blob/master/examples/ReaderLike.hs. See also this SO answer for an example of use: http://stackoverflow.com/a/29929718/1364288 – danidiaz May 10 '15 at 10:45

2 Answers2

4

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.

hasufell
  • 533
  • 3
  • 9
  • How is it different from implicit parameters. If `f` call `g` which needs the constraint, does `f` needs the constraint as well ? If not, does `g` get the value ? – mb14 May 11 '15 at 15:44
  • Yes, `f` needs the constraint as well. I have updated the code. Also, your question is excellent and I can only vaguely answer it. But the paper which reflection is based on http://okmij.org/ftp/Haskell/tr-15-04.pdf mentions several advantages over "implicit parameters" in the Introduction and Section 6.2. Reflection is supposed to play more "nicely" with the type system and supports concurrent sets of parameters better. – hasufell May 11 '15 at 17:08
  • `give` is Considered Evil. Edward Kmett has indicated that if other people wouldn't object so strongly, he would remove it altogether. – dfeuer May 11 '15 at 19:00
  • Interesting, can you elaborate more on that or give a reference? – hasufell May 11 '15 at 22:44
  • 1
    http://ircbrowse.net/selection/haskell?title=Conversation&events=20652226,20652227,20652228,20652233,20652235,20652237,20652239,20652240,20652245,20652246,20652252,20652254,20652258,20652259 – Edward Kmett May 12 '15 at 15:00
0

What you are asking, if it was possible would break referential transparency, at least for pure function ( a pure function result can depend on some global variables but not on a config file couldn't it ) ?

Usually people avoid that type of situation by passing implicitly the configuration as data via a Monad. Alternatively (if you are happy to refactor your code a bit) you can use the implicit parameter extenson, which in theory has been made to solve that type of problem but in practice doesn't really work. However, if you really need, you can use unsafePerformIO and ioRef to have a top level mutable state which is dirty and frowned upton. You need a top level mutable state, because you need to be able to modify "mutate" your initial config when you are loading it.

Then you get things like that :

myGlobalVar :: IORef Int
{-# NOINLINE myGlobalVar #-}
myGlobalVar = unsafePerformIO (newIORef 17)
mb14
  • 22,276
  • 7
  • 60
  • 102