-- The UndecidableInstances here is benign, just for the "advanced"
-- example at the end.
{-# LANGUAGE FlexibleContexts, ScopedTypeVariables, UndecidableInstances #-}

-- I don't demonstrate the advantages over implicit parameters here,
-- like multiple reifications of the same thing coexisting in
-- different, statically-checked contexts, etc.; this is intended as a
-- comparison to Reader(T).

import Data.Proxy
import Data.Reflection
import System.IO

data MyConfig = MyConfig
  { magic  :: Bool
  , volume :: Integer
  }

data Report p = Report
  { magicality :: String
  , loud :: Bool
  } deriving (Show)

-- some arbitrary thing we do calculations with
newtype Datum p = Datum Integer deriving (Read, Show)

report :: forall p. (Reifies p MyConfig) => Report p
report = Report
  { magicality = if magic conf then "Magical." else "Not so magical..."
  , loud = volume conf >= 11
  } where
  conf = reflect (Proxy :: Proxy p)

calculate :: forall p. (Reifies p MyConfig) => Datum p -> Datum p -> Datum p
calculate (Datum m) (Datum n) = Datum ((m+n) * volume conf) where
  conf = reflect (Proxy :: Proxy p)

run :: forall p. (Reifies p MyConfig) => Proxy p -> IO ()
run _ = do
  d1 <- ask "Datum 1:" :: IO (Datum p)
  d2 <- ask "Datum 2:" :: IO (Datum p)
  -- look ma, no plumbing
  print $ calculate d1 (calculate d2 d1)
  print (report :: Report p)

ask :: Read s => String -> IO s
ask prompt = do
  putStr prompt
  putChar ' '
  hFlush stdout
  readLn

main :: IO ()
main = do
  conf <- MyConfig <$> ask "Magic?" <*> ask "Volume:"
  reify conf run

-- If you're feeling adventurous, here is something we could not do
-- with ReaderT:
instance Reifies p MyConfig => Num (Datum p) where
  (+) = calculate
  m * n
    | magic conf = m + n
    | otherwise  = Datum 0  -- sorry, no magic for you.
    where conf = reflect (Proxy :: Proxy p)
  abs = undefined
  signum = undefined
  (-) = undefined
  negate = undefined
  fromInteger = Datum
