Iter Ator
Iter Ator

Reputation: 9269

Haskell Ambiguous Occurrence (+) when defining a vector

I would like to define a 3D vector, with x, y, z coordinates. Vectors can be added using the (+) operator and the length can be calculated using the length function

I get the following error, if I would like to compile it:

It could refer to either `Prelude.+',
                             imported from `Prelude' at hello.hs:1:1
                             (and originally defined in `GHC.Num')
                          or `Main.+', defined at hello.hs:9:14

The code is:

data Vec3 = Vec3 {
    x :: Float,
    y :: Float,
    z :: Float
} deriving (Eq,Show)

(+) :: Vec3 -> Vec3 -> Vec3
(Vec3 a b c) + (Vec3 t u w) = Vec3 (a+t) (b+u) (c+w)

length :: Vec3 -> Float
length (Vec3 a b c) = sqrt( a*a + b*b + c*c )

vv = Vec3 1.5 0.7 2.2

main :: IO ()
main = do
  print $ length vv

Upvotes: 3

Views: 427

Answers (3)

leftaroundabout
leftaroundabout

Reputation: 120711

To literally overload the + operator, you would need to define a Num instance, like

instance Num Vec3 where
  Vec3 a b c + Vec3 t u w = Vec3 (a+t) (b+u) (c+w)
  Vec3 a b c * Vec3 t u w = ...

This is in fact what the linear and hmatrix libraries do for their vector types, as does (basically) also the highly popular NumPy Python framework.

I would strongly recommend against this, because the semantics of vector spaces are in quite some sense incompatible with those of plain scalar numbers. In particular, you need to define multiplication here; the only way that properly works with these type signatures is component-wise, like with Matlab's .* operator

  Vec3 a b c * Vec3 t u w = Vec3 (a*t) (b*u) (c*w)

but that doesn't make mathematically sense for vectors as such, only for the expansion of vectors in a particular basis. Also it leads easily to bugs if you can erroneously define a vector as a single number (which in linear pastes the number into all vector components, urgh!)

A better fit is the Monoid class, as suggested by Reaktormonk. However you'll probably find yourself also wanting a scaling operation

(*^) :: Float -> Vec3 -> Vec3
μ *^ Vec3 t u w = Vec3 (μ*t) (μ*u) (μ*w)

(unlike component-wise multiplication, this is defined for any vector space), and Monoid doesn't offer this.

The correct class for this sort of type is VectorSpace.

instance AdditiveGroup Vec3 where
  Vec3 a b c ^+^ Vec3 t u w = Vec3 (a+t) (b+u) (c+w)
  negateV v = (-1)*^v
instance VectorSpace Vec3 where
  type Scalar Vec3 = Float
  μ *^ Vec3 t u w = Vec3 (μ*t) (μ*u) (μ*w)

Upvotes: 4

Reactormonk
Reactormonk

Reputation: 21690

I tried going around by

import Prelude hiding ((+), length)

but then you don't have access to addition anymore. I'd recommend going the Monoid route here. I'd rename length to vlength or similar, because IIRC there's no direct concept of that in any typeclass.

import Data.Monoid

data Vec3 = Vec3 {
    x :: Float,
    y :: Float,
    z :: Float
} deriving (Eq,Show)

instance Monoid Vec3 where
  mappend (Vec3 a b c) (Vec3 t u w) = Vec3 (a+t) (b+u) (c+w)
  mempty = Vec3 0 0 0

vlength :: Vec3 -> Float
vlength (Vec3 a b c) = sqrt( a*a + b*b + c*c )

vv = Vec3 1.5 0.7 2.2
v2 = Vec3 1.0 2.7 3.4

main :: IO ()
main = do
  print $ vv <> v2
  print $ vlength vv

Upvotes: 3

Electric Coffee
Electric Coffee

Reputation: 12104

The answer is simple: you're trying to overload the + operator, which is already defined in the Prelude.

Haskell doesn't technically allow operator overloading the way most other languages do, so you can't just define a new function called + because this function is already used for adding scalar numbers together.

Instead you could try calling it something else, like addV, or maybe >+< or something

Upvotes: 3

Related Questions