Sat 15 Aug 2009
Clearer Reflections
Posted by Edward Kmett under Haskell , Monads , Monoids , Type Hackery[7] Comments
I have updated the reflection package on hackage to use an idea for avoiding dummy arguments posted to the Haskell cafe mailing list by Bertram Felgenhauer, which adapts nicely to the case of handling Reflection. The reflection package implements the ideas from the Functional Pearl: Implicit Configurations paper by Oleg Kiselyov and Chung-chieh Shan.
Now, you no longer need to use big scary undefineds throughout your code and can instead program with implicit configurations more naturally, using Applicative and Monad sugar.
*Data.Reflection> reify (+) (reflect < *> pure 1 < *> (reflect < *> pure 2 < *> pure 3)) > 6
The Monad in question just replaces the lambda with a phantom type parameter, enabling the compiler to more readily notice that no instance can actually even try to use the value of the type parameter.
An example from the old API can be seen on the Haskell cafe.
This example can be made appreciably less scary now!
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, Rank2Types, FlexibleContexts, UndecidableInstances #-} import Control.Applicative import Data.Reflection import Data.Monoid import Data.Tagged newtype M s a = M a instance Reifies s (a,a → a → a) ⇒ Monoid (M s a) where mempty = tagMonoid $ fst < $> reflect a `mappend` b = tagMonoid $ snd < $> reflect < *> monoidTag a < *> monoidTag b monoidTag :: M s a → Tagged s a monoidTag (M a) = Tagged a tagMonoid :: Tagged s a → M s a tagMonoid (Tagged a) = M a withMonoid :: a → (a → a → a) → (∀s. Reifies s (a, a → a → a) ⇒ M s w) → w withMonoid e op m = reify (e,op) (monoidTag m)
And with that we can cram a Monoid dictionary -- or any other -- with whatever methods we want and our safety is assured by parametricity due to the rank 2 type, just like with the ST monad.
*> withMonoid 0 (+) (M 5 `mappend` M 4 `mappend` mempty) 9
[Edit: factored Tagged out into Data.Tagged in a separate package, and modified reflection to use that instead, with an appropriate version bump to satisfy the package versioning policy]
August 15th, 2009 at 9:26 am
Is the `Unused` typeclass defined in version 0.2.0 of the reflection package a pun? It seems, like the name suggests, neither used nor exported.
August 15th, 2009 at 9:41 am
@Sebastian:
Actually, I went through and tried to minimize the number of language extensions the package required. Two of the ones I eliminated were EmptyDataDecls and KindSignatures, since you can define a ‘bottom only’ datatype with a newtype wrapper, and you can perform some shenanigans to avoid the kind signatures once it is a newtype.
However, when you don’t use the newtype’s constructor anywhere, -Wall gives you a warning. Rather than have the module spit out 8-9 warnings, I wrote the Unused typeclass to give a single reference to each constructor and to silence the compiler.
August 15th, 2009 at 11:20 am
Somehow spurious extra spaces are getting inserted into your code…
Perhaps a bug in whatever script is responsible for converting characters to HTML character codes?
August 15th, 2009 at 11:24 am
Haha, and then the example I copied into my comment was deleted from the comment entirely.
Let’s try… < *>
I’m always amazed at how badly blog software manages to screw up text processing.
August 15th, 2009 at 12:17 pm
@Cale:
Yeah, it seems to be a bug in the package I’m using for syntax highlighting. =/ I swapped it out like 3 times, and eventually just moved on. If you have a suggestion for a good WordPress syntax highlighter that does Haskell, I’m all ears.
August 22nd, 2009 at 7:54 pm
Is there a reason not to make a Monoid instance of Tagged, skipping M and monoidTag and tagMonoid?
August 23rd, 2009 at 8:39 am
@Sjoerd:
The problem with that is that Monoid isn’t the only kind of thing we might want to reify into a type so we can reflect it as a dictionary.
If any other type had the same dictionary shape, and you took that approach then you could very easily conflate instances.
If the reflection library wanted to concern itself with every type class in Haskell, that would work fine, but otherwise, you would be stuck with orphan instances. ;)