views:

124

answers:

2

Related to my earlier question on traversing data structures, I'm having a problem making my code generic when I use it along with the uniplate package. I'm dealing with the data structures in the Language.Exts.Annotated.Syntax module, which are all generic with a type parameter l. This l is the same throughout the tree.

The kind of code I'm writing is like this:

doInt :: Child1 l -> Child1 l
doInt (Child1 l n) = Child1 l (n + 1)

doString :: Child2 l -> Child2 l
doString (Child2 l (_:s)) = Child2 l ('j' : s)

replace :: Data l => Parent l -> Parent l
replace = transformBi doInt
        . transformBi doString

This code produces the following error on both of the last two lines:

Ambiguous type variable `l' in the constraint:
  `Data l' arising from a use of `transformBi' at Test.hs:31:10-52
Probable fix: add a type signature that fixes these type variable(s)

I can see why this code is ambiguous: transformBi accepts a (to -> to) and from and turns it into a from; in my case there's no link between the l in Child1 l and the l in Parent l. What I don't see is how to fix it. I've tried adding a type constraint like transformBi (doInt :: Child1 l -> Child1 l), but I get the same error; it's as if I'm introducing a new l when I do this.

How can I tell the compiler that I'm using the same l for replace, transformBi doInt and transformBi doString?

Edit: Here is the full program that demonstrates what I'm doing. Under GHC 6.10.4, this program fails to compile, with the above error.

A: 

l should be of same type in function replace: Define it like:

data L = LInt Integer| LString String

See, replace can't be a polymorphic function. It uses strict types. This types defined by operations:

Prelude> :t (+)
(+) :: (Num a) => a -> a -> a

and

Prelude> :t (:)
(:) :: a -> [a] -> [a]

and

Prelude> :t 'c'
'c' :: Char

To make replace polymorphic you have to make it of polymorphic functions.

Vasiliy Stavenko
I can't change the types that replace deals with because they come from another package: see the earlier question I linked to. The purpose of uniplate is to allow you to write a generic function across any data structure. I'll post the code to clarify.
Tim Robinson
This is what polymorphic functions are used for. Please tell me what is the purpose of `replace` function, it not clear for me. And type signature for `transformBi`
Vasiliy Stavenko
transformBi :: (b -> b) -> a -> a. It's part of the uniplate package: http://hackage.haskell.org/packages/archive/uniplate/latest/doc/html/Data-Generics-Biplate.html#v:transformBi
Tim Robinson
In this example, I'l using it to walk a tree of type Parent and perform operations on nodes of type Child1 and Child2. The process is analogous to reflection in .NET.
Tim Robinson
+8  A: 

It looks like you need the scoped type variables extension.

{-# LANGUAGE ScopedTypeVariables #-}

replace :: forall l. Data l => Parent l -> Parent l
replace = transformBi (doInt :: Child1 l -> Child1 l)
        . transformBi (doString :: Child2 l -> Child2 l)

Note that quantification must be explicit to bring l into scope.

Alexey Romanov
Marvellous, works like a charm. Thanks.
Tim Robinson