-
Notifications
You must be signed in to change notification settings - Fork 44
Reflection
import Data.Reflection (Given (given), give)The reflection library is used to solve the configuration problem,
which is when we have some value (usually, configuration information)
that must be shared between many parts of a program.
Let's consider modular arithmetic as an example:
newtype Modulus = Modulus { unModulus :: Integer }
addMod :: Modulus -> Integer -> Integer -> Integer
addMod (Modulus m) a b = (a + b) `mod` m
eqMod :: Modulus -> Integer -> Integer -> Bool
eqMod (Modulus m) a b = (a `mod` m) == (b `mod` m)Here we have implemented ordinary modular addition and equality in a simple way. We can compute elementary facts such as:
eqMod (Modulus 3) 1 4 == TrueaddMod (Modulus 3) 2 2 == 1- ... and so on.
There are two things about this approach that are not nice:
- We have to specify the
Modulusat every function call. - This interface permits rather strange expressions, such as
eqMod (Modulus 3) 1 (addMod (Modulus 5) 2 2). Is such an expression even well-formed? It's hard to say.
Because Haskell isn't dependently typed, values are not types,
but reflection allows us to reflect values in types.
Then, type class instance resolution can solve our two awkward problems by:
- Inferring the correct value at each call site, so that we do not have to specify it every time.
- Because we do not specify the
Modulusat every call site, the risk of using different values is significantly diminished.
We alter our definitions of addMod and eqMod slightly:
addMod :: Given Modulus => Integer -> Integer -> Integer
addMod a b = (a + b) `mod` m
where Modulus m = given @Modulus
eqMod :: Given Modulus => Integer -> Integer -> Bool
eqMod a b = (a `mod` m) == (b `mod` m)
where Modulus m = given @ModulusTo use the modified functions, we insert a call to give above any modular arithmetic expression.
For example,
give (Modulus 3) (eqMod 1 4) == Truegive (Modulus 3) (addMod 2 2) == 1
(The parentheses around eqMod and addMod are actually optional.)
We can still construct our ill-conceived expression:
(give (Modulus 3) eqMod) 1 ((give (Modulus 5) addMod) 2 2)
but we are unlikely to do so accidentally
because we are not specifying the Modulus at every call site.
We can go further, and actually rule out the problematic expression,
by introducing a phantom type parameter to Modulus and a newtype wrapper around Integer.
However, this goes beyond merely demonstrating the use of Given,
so we leave it as an exercise for the reader.