3

I have a data structure (it's a specific subclass of rose-tree that forms a lattice with greatest-lower bound and lowest-upper bound functions), and it supports two perfectly reasonable functions to serve as the Monoid class's mappend.

Is there any way to support anonymous Monoid instances in haskell? Is this an instance where I should consider using something like Template-Haskell to generate my typeclasses for me?

What I'd love is a makeMonoid :: (RT a -> RT a -> RT a) -> Monoid a to let me create the instance on the fly, but I understand that that's incoherent with the stock typesystem as I understand it. I'm okay with it if I just need to pick a default merge function and write newtypes for other merges, just curious

jcc333
  • 759
  • 5
  • 15
  • 4
    If you have several instances and don't want to use `newtype`s everywhere you can [pass a "`Monoid`" dictionary around manually](http://channel9.msdn.com/posts/MDCC-TechTalk-Classes-Jim-but-not-as-we-know-them) as `data RTMonoid a = { empty :: RT a, append :: RT a -> RT a -> RT a}` and `rt1 :: RTMonoid a; rt1 = RTMonoid ...`; `rt2 :: RTMonoid a; rt2 = RTMonoid ...`, then you can write your functions to accept an `RTMonoid a` parameter that you use instead. It's a little clunkier having to pass it around manually, but it makes it more extensible. If you only have 2 instances, use newtypes. – bheklilr Feb 21 '15 at 19:06
  • 1
    You don't *have* to pick a default at all—it may be better to force the user to choose using a newtype. You can also use separate functions to `mergeThisWay` and `mergeThatWay`. – dfeuer Feb 21 '15 at 19:28
  • 2
    You might use the typeclasses in http://hackage.haskell.org/package/lattices Personally, I wish lattices were used more frequently in Haskell. They're such a useful structure. – Rein Henrichs Feb 21 '15 at 23:31

1 Answers1

3

You can create "local" instances of Monoid on the fly, using the tools in the reflection package. There's a ready-made example in the repository. This answer explains it a little.

This is a newtype wrapper over values of type a, on which we will define our Monoid instance.

newtype M a s = M { runM :: a } deriving (Eq,Ord)

Notice that there is a phantom type s that does not appear in the right hand side. It will carry extra information necessary for the local Monoid instance to work.

This is a record whose fields represent the two operation of the Monoid class:

data Monoid_ a = Monoid_ { mappend_ :: a -> a -> a, mempty_ :: a }

The following is the Monoid instance definition for M:

instance Reifies s (Monoid_ a) => Monoid (M a s) where
    mappend a b        = M $ mappend_ (reflect a) (runM a) (runM b)
    mempty = a where a = M $ mempty_ (reflect a)

It says: "whenever s is a type-level representation of our Monoid dictionary Monoid_, we can reflect it back to obtain the dictionary, and use the fields to implement the Monoid operations for M".

Notice that the actual value a passed to reflect is not used, it is passed only as a "proxy" of type M a s that tells reflect which type (s) to use to "bring back the record".

The actual local instance is constructed using the reify function:

withMonoid :: (a -> a -> a) -> a -> (forall s. Reifies s (Monoid_ a) => M a s) -> a
withMonoid f z v = reify (Monoid_ f z) (runM . asProxyOf v)

asProxyOf :: f s -> Proxy s -> f s
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 one in the Proxy supplied by reify.

danidiaz
  • 26,936
  • 4
  • 45
  • 95
  • An example of how to use `withMonoid` would add greatly to the introductory literature on `Data.Reflection`. – Cirdec Feb 21 '15 at 22:26
  • @Cirdec Another good example would be to dynamically generate `FromJSON`/`ToJSON` instances for a type, depending on information obtained at runtime. Like, asking the user for a prefix to be added to each field name. – danidiaz Feb 22 '15 at 00:21