views:

123

answers:

3

I am trying to write an fmap for this type

data Triangle a  = Triangle {t0 :: Point a, t1 ::  Point a, t2 ::  Point a}

where Point is defined as

data Point a = Point {px :: a, py :: a, pz :: a}

And my instance def is

instance Functor Triangle where 
    fmap f (Triangle v0 v1 v2) = Triangle (f v0) (f v1) (f v2)

I am getting the following compliation error and I can't figure out why

 C:\Scripts\Haskell\Geometry.hs:88:1:
     Occurs check: cannot construct the infinite type: a = Point a
     When generalising the type(s) for `fmap'
     In the instance declaration for `Functor Triangle'

Any ideas?

+8  A: 
instance Functor Point where
    fmap f (Point v0 v1 v2) = Point (f v0) (f v1) (f v2)

instance Functor Triangle where
    fmap f (Triangle v0 v1 v2) = Triangle (fmap f v0) (fmap f v1) (fmap f v2)

In the Triangle instance, f is a -> b. We have to convert it to Point a -> Point b first. Then we can make fmap f transform Triangle a to Triangle b. (Observe you're applying f to 9 objects, if I understood your intention correctly) [edit: was 27]

sdcvvc
Got it. I didn't realize that the fmap had to be applied to the components like that. I realize the same is true with Traversable. Thanks!
Jonathan Fischoff
+7  A: 

The previous answer gives you a correct solution, but it might be helpful to be more explicit about what's going on here. The type of fmap is

fmap :: Functor f => (a -> b) -> f a -> f b

So the type inference for your instance declaration proceeds as follows:

  1. In fmap f (Triangle v0 v1 v2), f must have some type a -> b and (Triangle v0 v1 v2) must have type Triangle a.
  2. By the definition of Triangle, v0, v1, and v2 must have type Point a.
  3. Since f is applied to v0, v1, and v2, its argument type a must be Point a.
  4. Oops, a = Point a is unsatisfiable.

Why does the definition Triangle (fmap f v0) (fmap f v1) (fmap f v2) work? :

  1. In fmap f (Triangle v0 v1 v2), f must have some type a -> b and (Triangle v0 v1 v2) must have type Triangle a.
  2. By the definition of Triangle, v0, v1, and v2 must have type Point a.
  3. Assuming Point is an instance of Functor, as above, fmap f v0 must have type Point b, where b is the result type of f. Likewise for v1 and v2.
  4. Hence Triangle (fmap f v0) (fmap f v1) (fmap f v2) has type Triangle b.
  5. QED.
Chris Conway
I find this kind of walk-through very valuable when learning how type inference works, thanks Chris!
janne
+2  A: 

Btw, an interesting property of Functor is that there is only one possible instance to make that will satisfy the Functor laws.

Better yet, this instance can be automatically produced for you using the derive package:

{-# LANGUAGE TemplateHaskell #-}

import Data.DeriveTH (derive, makeFunctor)

data Point a = Point {px :: a, py :: a, pz :: a}
$(derive makeFunctor ''Point)

data Triangle a  = Triangle {t0 :: Point a, t1 ::  Point a, t2 ::  Point a}
$(derive makeFunctor ''Triangle)

Imho this is a win partially because if you decide to change the definition of Triangle, its Functor instance is maintained automatically for you.

yairchu
The derive version in cabal was not functional at the time that I wrote this question. However, I am now using the darc version for this stuff.
Jonathan Fischoff
@Jonathan Fischoff: yeah I guess I was using the darcs version as well
yairchu