Why is my overloaded definition of (+) ambiguous?

data Point = Point Float Float deriving (Show, Ord, Eq)

(+) :: Point -> Point -> Point
(Point x1 y1) + (Point x2 y2) = Point (x1 + x2) (y1 + y2)

Gives me an error on loading into ghci.

It seems to me that x1, x2, y1, y2 should be easily inferred as Float - because they are when I declared them as arguments to the Point constructor.

src/Shapes.hs:4:53: error:
    Ambiguous occurrence ‘+’
    It could refer to
       either ‘Prelude.+’,
              imported from ‘Prelude’ at src/Shapes.hs:1:1
              (and originally defined in ‘GHC.Num’)
           or ‘Main.+’, defined at src/Shapes.hs:4:15
  |
4 | (Point x1 y1) + (Point x2 y2) = Point (x1 + x2) (y1 + y2)

Yet (Point 0 0) < (Point 1 1) works without throwing an error.

  • What is my mistake?
  • How should I perform this overloading idiomatically?

This answer is relevant: haskell - How to define (+) function for custom data type? - Stack Overflow

The Prelude.+ function only works on numbers, specifically Num typeclass. You can’t easily implement instance Num Point because you’d need to decide what the signum of your Point means (is it a positive or a negative vector?)
Please note that e.g. list concatenation is (++) and it similarly works on lists only.

You don’t “overload” the default (+) function for lists and vectors, because it is a function, not an “operator” feature. If you want (+) to work like operator+ in C++, you’d have to shadow Prelude.+ and completely replace it with another function, much more generic, maybe of class Summable a where (+) :: a -> a -> a, with instance for Point, Float, [] and other types you want to “sum”.

If you are replacing Prelude.+ with Main.+ everywhere in your code to make it more general, hide/shadow the original “plus” and only call it by fully qualified name Prelude.+, that’s what the “Ambiguous occurrence” error is complaining about.

Or better, just use a proper vectors/matrices library for your linear algebra and sidestep the whole issue!

The error from ghc is saying: “you’ve called +, but I don’t know which one you are referring to, is that the + defined in src/Shapes.hs, or the one in Prelude?”

I would guess it’d work fine if you renamed your + to plus or some other name, have you tried that?

1 Like

Thanks, yes I had tried that - I wanted to use infix ‘+’

Thanks for your detailed reply @sid.

Looks like my error will teach me something. I was expecting to be able to stick my function to the types like in Smalltalk or in particular - Julia. I don’t do C++

Is just a type exercise I made up while doing something else - not a linear algebra package. I wouldn’t presume :slight_smile:

I need to get the Julia v. Haskell models.

Cheers…

2 Likes

While there are other, more robust ways (as noted above), if you don’t import prelude, you won’t have the collision between your definition of + and the one in the prelude.

You can’t easily implement instance Num Point because you’d need to decide what the signum of your Point means (is it a positive or a negative vector?)

Isn’t abs x * signum x == x the only law relevant to signum? So couldn’t you get by with this lawful-but-useless definition of it?

instance Num Point where
  -- ...
  abs = id
  signum _ = 1

Or actually, couldn’t the whole instance just be implemented exactly like the one for Complex Float is?

Good point - if you can easily decide, you can easily implement. I’ve said it wrongly and I stand corrected.

Apparently the only reason I said “you can’t easily” is that I’ve trained myself not to do “lawful evil” hacks lightly :slight_smile: The letter says abs = id is valid, but (my) spirit demands abs to be a norm, like the Euclidean norm

{-# LANGUAGE ExistentialQuantification #-}
data Point numType = Num numType => Point numType numType
myAbs :: Point n -> n
myAbs (Point x y) = x*x + y*y -- sqrt omitted

And the number class should be general enough to include all numbers, including complex!
So I’d really rather rework the model than simply satisfy the compiler and the Prelude with a quick “fix”.

Yes, you can do it exactly how you like!
I’d go with a Vec2 vector, but you can use complex, sure.

You don’t need to go all abs = id. A pointwise abs satisfies the Num laws. I’m not sure if that’s still “lawful evil” or “lawful neutral”.

instance Num Point where
  fromInteger n = Point (fromInteger n) (fromInteger n)
  (Point x1 y1) + (Point x2 y2) = Point (x1 + x2) (y1 + y2)
  (Point x1 y1) * (Point x2 y2) = Point (x1 * x2) (y1 * y2)
  abs (Point x y) = Point (abs x) (abs y)
  signum (Point x y) = Point (signum x) (signum y)
  negate (Point x y) = Point (negate x) (negate y)

Ah, that instance is definitely a choice, and I think a pretty reasonable one. In particular, Point is isomorphic to base's Op Float Bool, and its Num instance is equivalent to yours.