13

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}
Nathan Howell
  • 4,627
  • 1
  • 22
  • 30
  • By my reading of the uniplate documentation, the type of `instances` ought to be just `[Baz]`, no? – Daniel Wagner Oct 13 '11 at 20:57
  • Yes, it's more a theoretical question than anything. – Nathan Howell Oct 13 '11 at 21:52
  • 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 Oct 13 '11 at 21:57
  • I've updated the question, let me know if it's still too vague. – Nathan Howell Oct 13 '11 at 22:17
  • 7
    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 Oct 13 '11 at 23:52
  • 2
    Looks like it might be possible with ConstraintKinds in GHC 7.4 – Nathan Howell Oct 14 '11 at 16:17
  • I wrote something like this for a typeclass-generic fold over an HList in GHC head, but you need a witness of what typeclass you want to use: http://hpaste.org/53413 . Not quite what you're looking for, since it's not using generics, but it's the same idea. The witness is just so you can call it. You can write the function without using the witness, but you'll never be able to call it, because to do so would require higher-order unification of the constraint. – copumpkin Nov 01 '11 at 22:09
  • @pumpkin That does look quite useful, if one of the generics libraries worked like this we'd be in business. Thanks. – Nathan Howell Nov 02 '11 at 16:52

2 Answers2

1

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

Dmitry Dzhus
  • 600
  • 2
  • 8
  • Link no longer works but is archived: https://web.archive.org/web/20210616103013/http://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/ – user9538 Jul 28 '22 at 17:54
0

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.

max
  • 811
  • 6
  • 13
  • What I want is a generic function, similar to universeBi (from uniplate) that abstracts over typeclasses instead of types. I have functions like the one you're recommending already, but I have a lot of them and I'm trying to simplify a very large chunk of code. – Nathan Howell Nov 29 '11 at 07:01