Another way of solving this problem is to use the reflection
library. This is definitely a sophisticated method and should be used with great care. There's a tutorial resource on it by Austin Seipp available at FP Complete that goes into its implementation, but that can be quite hairy.
The simplified form of reflection
is the Given
typeclass. To do that, we simply annotate our functions with given
wherever we desire some global configured value.
meth1 = 1 + given
meth2 = given - 1
Like ImplicitParams
we get notice of our use of Given
in the type signatures.
meth1 :: (Num a, Given a) => a
meth2 :: (Num a, Given a) => a
This allows us to statically eliminate them and ensure that all proper configuration information has been passed using give
give :: a -> (Given a => r) -> r
So that if we have someComplexCalculation :: Given Config => Result
we can solve that by providing a Config
.
give config someComplexCalculation :: Result
and the types ensure that we didn't let this missing configuration propagate to the top.
The Reifies
/reflect
/reify
machinery generalizes this property, but does so at a loss of some convenience and simplicity. It's needed if you must pass multiple configurations which overlap—using Given
it'd be impossible for the system to know which given
s should be replaced by the passed configuration under the overlapping region, Reifies
solves that with an s
parameter much like the ST
monad's s
parameter.
The killer application for Reifies
however is passing runtime configured typeclasses to expressions. Normally you can't override typeclasses, but Reifies
gives a trick way of doing that. See Austin Seipp's tutorial toward the bottom for more details about that.