Numeric operations in Num typeclass are defined with type :: Num n => n -> n -> n , so both operands and the return value must be of the same type. It is not possible to modify an existing class, so your parameters must either define new operators, or hide the existing Num class and completely replace it with your own implementation.
To implement operators that can have different types of operands, you will need several language extensions.
{-
Instead of a Num class that spans + , - and * , it is more flexible to define different types of stacks for different operands, because when Point3D * Double makes sense, Point3D + Double usually does not. Start with Mul .
class Mul abc | ab -> c where (|*|) :: a -> b -> c
Without extensions in type classes, there is only one type parameter, but with MultiParamTypeClasses we can declare a class of type Mul for a combination of types a , b and c . Part by parameters | ab -> c | ab -> c is a "functional dependency", which in this case claims that type c depends on a and b . This means that if we have an instance like Mul Double Point3D Point3D , then the functional dependency states that we cannot have any other instances of Mul Double Point3D c , where c something other than Point3D , i.e. The return type of multiplication is always explicitly determined by the type of operands.
Here we implement instances for Mul :
instance Mul Double Double Double where (|*|) = (*) instance Mul Point3D Double Point3D where Point3D xyz |*| a = Point3D (x*a) (y*a) (z*a) instance Mul Double Point3D Point3D where a |*| Point3D xyz = Point3D (x*a) (y*a) (z*a)
This flexibility does not come without its reservations, though, because it will make type inference much more difficult for the compiler. For example, you cannot just write
p = Point3D 1 2 3 |*| 5
Because the letter 5 not necessarily Double . It can be any Num n => n , and it is quite possible that someone announces new instances of the Mul Point3D Int Int , which behaves in a completely different way. So this means that we must explicitly specify the types of numeric literals.
p = Point3D 1 2 3 |*| (5 :: Double)
Now, if instead of defining new operands we want to override the default Num class from Prelude , we can do it like this
import Prelude hiding (Num(..)) import qualified Prelude as P class Mul abc | ab -> c where (*) :: a -> b -> c instance Mul Double Double Double where (*) = (P.*) instance Mul Point3D Double Point3D where Point3D xyz * a = Point3D (x*a) (y*a) (z*a)