In the simplest use-case, if you have some configuration information you'd like to make generally available across a set of functions:
data Config = Config { w :: Int, s :: String }
you can add a Given Config
constraint to the functions that need access to the configuration:
timesW :: (Given Config) => Int -> Int
and then use the value given
to refer to the current configuration (and so w given
or s given
to refer to its fields):
timesW x = w given * x
With a few other functions, some that use the configuration and some that don't:
copies :: Int -> String -> String
copies n str = concat (replicate n str)
foo :: (Given Config) => Int -> String
foo n = copies (timesW n) (s given)
you can then run a computation under different configurations that you give
:
main = do
print $ give (Config 5 "x") $ foo 3
print $ give (Config 2 "no") $ foo 4
This is similar to:
defining given :: Config
globally, except you can run the computation under multiple configurations in the same program;
passing the configuration around as an extra parameter to every function, except you avoid the bother of explicitly accepting the configuration and passing it on, like:
timesW cfg x = w cfg * x
foo cfg n = copies (timesW cfg n) (s cfg)
using a Reader
monad, but you don't have to lift everything to an awkward monad- or applicative-level syntax, like:
timesW x = (*) <$> asks w <*> pure x
foo n = copies <$> timesW n <*> asks s
The full example:
{-# LANGUAGE FlexibleContexts #-}
import Data.Reflection
data Config = Config { w :: Int, s :: String }
timesW :: (Given Config) => Int -> Int
timesW x = w given * x
copies :: Int -> String -> String
copies n str = concat (replicate n str)
foo :: (Given Config) => Int -> String
foo n = copies (timesW n) (s given)
main = do
print $ give (Config 5 "x") $ foo 3
print $ give (Config 2 "no") $ foo 4