Instancing Functor requires that fmap is over _arbitrary functions (a
-> b). Because this is more general than (Color ci) => ci -> b, you're
not allowed to do that; letting the instance pass the typechecker
would allow mapping functions f whose input (type ci) is _not_ of the
Color typeclass.
Think of (Color ci) => ci -> b as reading, mathematically, for all ci
in the set InstancesOfTypeClassColor, this type checks if it's a term
of type ci -> b.
Some quick workarounds:
1. Relax the type constraint on Unary.
2. Make your own typeclass Functor that uses a Color ci restriction.
On Fri, Nov 4, 2011 at 2:47 PM, Phaedon Sinis <phaedo...@gmail.com> wrote:
data ImageTree2d c= Leaf { imageFn :: Point2d -> c }| Unary { unaryOp :: (Color ci) => ci -> c, -- yeah, it's different b/c maybe we're going to another color spacetreePtr :: (Color ci) => ImageTree2d ci -- but gosh I wish I could bind this to the same "ci" as in unaryOp}| Binary { binaryOp :: (Color ci) => ci -> ci -> c, -- same hereleftPtr :: (Color ci) => ImageTree2d ci,rightPtr :: (Color ci) => ImageTree2d ci}| Spatial { xformFn :: Point2d -> Point2d ,streePtr :: ImageTree2d c}
Just to add to Bryan's point, class constraints in data declarations
do not cause a dictionary to be placed in the data type. So they are
a) kind of useless, and b) contradict your intuition. All they do is
prevent code from compiling, but not ever actually allow anything new.
(Well, there are a couple of very fringe edge cases involving
defaulting or not of types like Num, but for the most part class
constraints in data declarations affect only the correctness, not the
semantics of code.)
Here's a prime example of why such constraints can be really
frustrating:
data (Show a) => Foo a = Foo a
instance Show (Foo a) where
show (Foo a) = "Foo " ++ show a -- compilation failure
This gives a compilation error saying "No instance for (Show a)".
Intuitively, a reasonable reaction might be, "What do you mean no
instance for Show a? Foo a *requires* a to be in class Show, so you
know a has to be in class Show? Why are you rejecting this code?"
The reason the code has to be rejected comes back to what I said in
lecture about always imagining that class constraints are hidden
dictionary arguments. Say you have a function like:
showFoo :: Foo a -> String
showFoo (Foo a) = show a -- error
This function has no (Show a) => constraint. Therefore, it does not
receive the Show dictionary for a. Therefore, it has no way of
calling the show method on a. Even though the compiler knows that
such a method exists, it doesn't have a pointer to it.
The solution, of course, is to say:
instance (Show a) => Show (Foo a) where
show (Foo a) = "Foo " ++ show a
But if you are going to sprinkle (Show a) constraints throughout all
your functions anyway, there's really not much value in having the
constraint also appear in the data type.
That said, if you want to pass a dictionary around explicitly, there
is a language extension called ExistentialQuantification that allows
you to do this. For example:
{-# LANGUAGE ExistentialQuantification #-}
data Bar = forall a. (Show a) => Bar a
instance Show Bar where
show (Bar a) = "Bar " ++ show a
This says that a "Bar" contains a value of type a, plus a dictionary
proving that a is of class show. The terminology is confusing,
because the term Existantial refers to the value (for all values of
type Bar, there exists a Show dictionary for the a). But the types
are declared using forall, making it seem like the language extension
should actually be "UniversalQuantification".
ExistantialQuantification is a fairly safe extension to use.
Control.Exception makes use of it, so the extension is probably here
to stay. However, it's not quite a drop in replacement for the Foo
example above, because the Bar type constructor does not take a type
argument. You might actually want to define some functions on, say
(Foo Int), so that they only work on int, and other functions on (Foo
a) for any a in the Show class. There is another less safe extension
called GADTs that lets you do that.
The basic idea with GADTs is that you define your constructors by
declaring their function types. For example:
{-# LANGUAGE GADTs #-}
data Baz a where
BazConstructor :: (Show a) => a -> Baz a
showBaz :: Baz a -> String
showBaz (BazConstructor a) = show a -- okay
(I used BazConstructor just to make clear where the constructor is,
but you could just as easily also call it Baz.) In this case, a Baz
using the BazConstructor includes a dictionary, so it is possible to
call show a even without a (Show a) constraint on the showBaz
function.
In addition to the fact that they are less well excepted, there are a
couple of other gotchas with GADTs. One is that enabling GADTs makes
all let bindings that would require ad-hoc polymorphism monomorphic
(unless you declare a type in the let binding). This is in contrast
to normal Haskell, where the compiler infers polymorphic types for
bindings that look like functions (see the DMR slides in the second
lecture).
A second gotcha is that the record syntax is fairly unintuitive. I
actually didn't even remember, and found that it works like this:
data Baz a where
Baz :: (Show a) => { unBaz :: a } -> Baz a
However, that appears to have changed in a recent version of GHC, so
older code uses a different syntax.
David
He I thought you may be wrong for a second here and so read through
and old blog post from Simon PJ,
http://hackage.haskell.org/trac/ghc/blog/LetGeneralisationInGhc7
May be an interesting read for some people. 7.2 has made mono local
binds the default (regardless of GADTs) but also relaxed the feature
so that certain let bindings are generalized even when MonoLocalBinds
is on.
Cheers,
David
Hmm... It does appear the situation is even more extreme than I
thought. With MonoLocalBinds, the compiler does not even generalize
parametric polymorphic types, let alone ad hoc ones.
> May be an interesting read for some people. 7.2 has made mono local
> binds the default (regardless of GADTs) but also relaxed the feature
> so that certain let bindings are generalized even when MonoLocalBinds
> is on.
I don't have GHC 7.2 handy to test on, but doesn't enabling
MonoLocalBinds by default constitute a rather serious departure from
haskell 2010? Is -XNoMonoLocalBinds really required to get Haskell
2010 semantics?
David
Setting '-XHaskell2010' is require to get Haskell 2010 semantics. The
default mode GHC uses differs slightly from this. Hmm looking through
the code for GHC just now though it doesn't appear MonoLocalBinds is
actually turned on by default. We can play around next time at the
office and see whats the current settings actually are.