14
votes

I have a collection of records spread across a number of types in a large Haskell application that reference each other. All of the types involved implement a common typeclass. The typeclass contains functions that work over a variable and all of its children, very much like uniplate's para function.

This is a simplified code sample of what I'd like to build. Is it possible (and reasonable) to get generic functionality to fold over record fields that implement a given typeclass in GHC...

{-# LANGUAGE RankNTypes #-}

myPara :: forall a r . (Data a, Foo a)
       => (forall b . Foo b => b -> [r] -> r)
       -> a
       -> r

-- or as a fold
myFold :: forall a r . (Data a, Foo a)
       => (forall b . Foo b => r -> b -> r)
       -> r
       -> b
       -> r

But generic enough to work with an arbitrary typeclass?

{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE DeriveDataTypeable #-}

import Data.Data
import Data.Generics.Uniplate.Data

class Foo a where 
  fooConst :: a -> Int

data Bar = Bar {barBaz :: Baz} deriving (Typeable, Data)

instance Foo Bar where
  fooConst _ = 2

data Baz = Baz {barBar :: Bar} deriving (Typeable, Data)

instance Foo Baz where
  fooConst _ = 3

func :: Int
func = foldl (\ x y -> x + fooConst y) 0 instances where
  instances :: forall a . (Data a, Foo a) => [a]
  instances = universeBi bar
  bar = Bar{barBaz = baz}
  baz = Baz{barBar = bar}

Compiling this with GHC 7.2.1 (obviously) fails:

Repro.hs:21:42:
    Ambiguous type variable `a0' in the constraints:
      (Data a0) arising from a use of `instances' at Repro.hs:21:42-50
      (Foo a0) arising from a use of `instances' at Repro.hs:21:42-50
    Probable fix: add a type signature that fixes these type variable(s)
    In the third argument of `foldl', namely `instances'
    In the expression: foldl (\ x y -> x + fooConst y) 0 instances
    In an equation for `func':
        func
          = foldl (\ x y -> x + fooConst y) 0 instances
          where
              instances :: forall a. (Data a, Foo a) => [a]
              instances = universeBi bar
              bar = Bar {barBaz = baz}
              baz = Baz {barBar = bar}
2
By my reading of the uniplate documentation, the type of instances ought to be just [Baz], no?Daniel Wagner
Yes, it's more a theoretical question than anything.Nathan Howell
I'm not sure I understand what the theoretical question is. Could you make that a bit more precise in your text? (When you ask, "Is it possible to get generic functionality like this?", what does "like this" mean?)Daniel Wagner
You are beginning to abstract over typeclasses, which Haskell is pretty bad at. Make the class concrete: eg. for Eq, use data Eq a = Eq { eq :: a -> a -> Bool }, then pass it as a parameter. Typeclasses are mainly for notational convenience, let functions do the heavy lifting.luqui
Looks like it might be possible with ConstraintKinds in GHC 7.4Nathan Howell

2 Answers

1
votes

You've hit the Existential Antipattern. You shouldn't be using typeclasses for anything except cases when you need compiler to guess the type for you. List of values of type x will stay the list of values of type x no matter what typeclasses you will implement, and you can't break the type system here.

You can:

  1. Use an ad-hoc box type as suggested above. This is just plain ugly.

  2. Implement generic interfaces with message-passing.

    data Foo = Foo { fooConst :: Int }

    bar = Foo 2

    baz = Foo 3

0
votes

been a while..

Have you tried existentially quantified data constructors?

data Foo = forall a. MyTypeClass a => Bar [a]

func (Bar l) = map typeClassMember a

now, func will work with anything of type Foo, which hides the inner type.