The reflection package
m@kotha.net
Overview
- What problem does the package solve?
- How is it implemented?
- How is it used in ad, an automatic differentiation package?
- Note:
- Slides are in English, talk will be in Japanese. Just interrupt me when you have a question.
The problem
- The package lets you parameterize a type over values.
- Example: modular arithmetic
-
newtype Mod n = Mod Int
Use a type class to recover a value from a type
class ReifiesInt n where
reflect :: proxy n -> Int
If n is a type encoding an integer, then reflect ([]::[n]) will be that integer.
newtype Mod n = Mod Int
instance (ReifiesInt n) => Num (Mod n) where
Mod a + Mod b = Mod (a + b `mod` reflect ([]::[n]))
...
Encoding a value into a type?
- Given (i::Int), you need to create a type (n :: *) whose ReifiesInt instance gives back i.
- (i::Int) might not be known until runtime.
Encoding Bools
- s/Int/Bool/g makes the problem much easier.
class ReifiesBool b where
reflect :: proxy b -> Bool
data TrueType -- Represents True
data FalseType -- Represents False
instance ReifiesBool TrueType where reflect = const True
instance ReifiesBool FalseType where reflect = const False
- Given these definitions, you can encode a runtime Bool value into a type:
reify :: Bool -> (forall b. ReifiesBool b => Proxy b -> a) -> a
reify True f = f (Proxy :: Proxy TrueType)
reify False f = f (Proxy :: Proxy FalseType)
Encoding Ints as binary numbers
- Following the same pattern, natural numbers can be encoded as a list of bits.
class ReifiesInt b where
reflect :: proxy b -> Int
data Zero -- represents 0
data Twice n -- represents (2*n)
data TwicePlus1 n -- represents (2*n+1)
instance ReifiesInt Zero where reflect = const 0
instance (ReifiesInt n) => ReifiesInt (Twice n) where
reflect _ = 2 * reflect ([]::[n])
instance (ReifiesInt n) => ReifiesInt (TwicePlus1 n) where
reflect _ = 1 + 2 * reflect ([]::[n])
Encoding Ints as binary numbers (cont.)
reify :: Int -> (forall n. ReifiesInt n => Proxy n -> a) -> a
reify n f
| n < 0 = error "cannot reify negative number"
| n == 0 = f (Proxy :: Proxy Zero)
| mod n 2 == 0 = reify (div n 2) $
\(_::Proxy m) -> f (Proxy :: Proxy (Twice m))
| otherwise = reify (div n 2) $
\(_::Proxy m) -> f (Proxy :: Proxy (TwicePlus1 m))
Encoding arbitrary Haskell values
The reflection package
- The package provides the Reifies class and the reify function:
class Reifies s a | s -> a where
reflect :: proxy s -> a
reify :: forall a r. a -> (forall s. Reifies s a => Proxy s -> r) -> r
- It can encode arbitrary Haskell values, but the implementation is simple.
The implementation
- It uses some knowledge about how GHC represents values at runtime.
- The second argument to reify has the following type:
Reifies s a => Proxy s -> r
GHC compiles this into a function that takes a dictionary containing all the methods of Reifies s a. So in runtime, the above type is equivalent to:
DICTIONARY_Reifies s a -> Proxy s -> r
Moreover, a dictionary for a single-method class is actually a newtype of that method:
(forall proxy. proxy s -> a) -> Proxy s -> r
Using these facts, you can unsafeCoerce the function, and give an arbitrary value as a 'dictionary' to it.
Reflection in ad
- reflection is being used to implement the Reverse mode in ad.
- The reverse mode needs to build an expression graph where each node is an arithmetic operation.
- This is implemented by accumulating nodes in a shared IORef, which is hidden inside a type variable using the reflection package.
EOF
Thank you for listening.