Folding over a polymorphic list in Haskell

1.3k Views Asked by At

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

There are 2 best solutions below

1
max On

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.

1
Dmitry Dzhus On

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