You can create "local" instances of Monoid on the fly using the tools in the reflection package. There is a ready-made example in the repository. This answer explains this a bit.
This is the newtype wrapper over values of type a , on which we will define our Monoid instance.
newtype M as = M { runM :: a } deriving (Eq,Ord)
Note that there is a phantom s type that does not appear on the right side. It will carry additional information necessary for the operation of the local Monoid instance.
This is a record whose fields represent two operations of the Monoid class:
data Monoid_ a = Monoid_ { mappend_ :: a -> a -> a, mempty_ :: a }
The following is a Monoid instance Monoid for M :
instance Reifies s (Monoid_ a) => Monoid (M as) where mappend ab = M $ mappend_ (reflect a) (runM a) (runM b) mempty = a where a = M $ mempty_ (reflect a)
It says: "whenever s is a level-level representation of our Monoid Monoid_ dictionary, we can flip it to get the dictionary and use the fields to implement Monoid operations for M ".
Please note that the actual value of a passed to reflect is not used, it is transmitted only as a “proxy” of type M as , which tells reflect which type ( s ) to use to “return the record”.
The actual local instance is created using the reify function:
withMonoid :: (a -> a -> a) -> a -> (forall s. Reifies s (Monoid_ a) => M as) -> a withMonoid fzv = reify (Monoid_ fz) (runM . asProxyOf v) asProxyOf :: fs -> Proxy s -> fs asProxyOf a _ = a
The asProxyOf function is a trick to convince the compiler that the phantom type used in the monoid is the same as the Proxy specified in reify .
source share