4

I'm working on an applicative functor that contains a monoid to "view" the execution. However, sometimes I don't care about this part at all, so the choice of monoid is irrelevant as it will never be consumed. I've simplified what I have into:

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}

import GHC.Exts

class Render a b where render :: a -> b
instance Render a () where render = const ()

class Merge a where
  type Renderer a b :: Constraint
  merge :: Renderer a b => a -> b

data Foo = Foo Bool

instance Merge Foo where
  type (Renderer Foo) m = (Render Bool m)
  merge (Foo b) = render b

Render is used to transform various as into a single b. Merge is a big simplification of my actual functor, but the point is it contains a type family/constraint and my intention of that is to specify exactly what Renderers a Merge requires.

Now, I might want to "run" the Merge, but discard the view, which is akin to something like:

runFoo :: Merge a => a -> Int
runFoo x = case merge x of () -> 5

But this will fail because:

Could not deduce (Renderer a ()) arising from a use of merge

I chose () as my monoid because forall a, we have an instance of Render a (). So if there was a way to say that Merge a just means a collection Render constraints then this would work fine. Of course, Merge a is more general than that - it could add arbitrary constraints, which explains the compilation error.

Is there anyway to achieve what I want without changing the signature of runFoo?

ocharles
  • 6,172
  • 2
  • 35
  • 46
  • Does `Renderer` always include exactly one `Render`? –  Jan 04 '13 at 13:55
  • @Tinctorius - no, generally depends on the amount of distinct types in the fields of `Foo` – ocharles Jan 04 '13 at 13:59
  • Is there a reason why `Merge` doesn't have `b` as a parameter as well? – C. A. McCann Jan 04 '13 at 14:08
  • @C.A.McCann - no reason why it doesn't, but I don't see much reason why it should either. If that would help things I could bring it in, but I can't immediately see how it would help. – ocharles Jan 04 '13 at 14:14
  • Having something as a class parameter in general gives you more options for expressing tricky constraints. Didn't have anything specific in mind, though. – C. A. McCann Jan 04 '13 at 14:20

1 Answers1

5

This might not scale if you have a lot of these cases, but this works:

class Renderer a () => Merge a where
  ...
Sjoerd Visscher
  • 11,840
  • 2
  • 47
  • 59
  • I hadn't considered that. I could read that constraint as '`Merge`s should at least support rendering with the `()` monoid'. However, this does incur `UndecidableInstances`. – ocharles Jan 04 '13 at 15:43
  • "UndecidableInstances is not a dangerous flag. It will never cause the type-checker to accept a program that `goes wrong.' The only bad consequence of using the flag is type checker's might be telling us that it cannot decide if our program is well-typed, given the context-stack--depth limit." http://okmij.org/ftp/Haskell/TypeClass.html#undecidable-inst-defense – Sjoerd Visscher Jan 04 '13 at 16:25
  • I know that, I already use it in 3 other places ;) But ideally I like to find solutions that don't require it, even if for purely academic reasons. I'll probably accept this answer, just leaving the ground open for a bit longer though. This is indeed working a treat! – ocharles Jan 04 '13 at 16:30