Suppose I have a custom type wrapping an existing type,
newtype T = T Int deriving Show
and suppose I want to be able to add up T
s, and that adding them up should result in adding the wrapped values up; I would do this via
instance Num T where
(T t1) + (T t2) = T (t1 + t2)
-- all other Num's methods = undefined
I think we are good so far. Please, tell me if there are major concerns up to this point.
Now let's suppose that I want to be able to multiply a T
by an Int
and that the result should be a T
whose wrapping value is the former multiplied by the int; I would go for something like this:
instance Num T where
(T t1) + (T t2) = T (t1 + t2)
(T t) * k = T (t * k)
-- all other Num's methods = undefined
which obviously doesn't work because class Num
declares (*) :: a -> a -> a
, thus requiring the two operands (and the result) to be all of the same type.
Even defining (*)
as a free function poses a similar problem (i.e. (*)
exists already in Prelude
).
How could I deal with this?
As for the why of this question, I can device the following
(Int,Int)
for 2D vectors in a cartesian plane,(Int,Int)
for another unrelated thing,newtype
for at least one of them or, if use (Int,Int)
for several other reasons, then why not making all of them newtype
s wrapping (Int,Int)
?newtype Vec2D = Vec2D (Int,Int)
represents a vector in the plain, it makes sense to be able to do Vec2D (2,3) * 4 == Vec2D (8,12)
.Very similar examples have been asked often already, and the answer is that this is not a number type and therefore should not have a Num
instance. What it actually is is a vector space type, accordingly you should define instead
{-# LANGUAGE TypeFamilies #-}
import Data.AdditiveGroup
import Data.VectorSpace
newtype T = T Int deriving Show
instance AdditiveGroup T where
T t1 ^+^ T t2 = T $ t1 + t2
zeroV = T 0
negateV (T t) = T $ -t
instance VectorSpace T where
type Scalar T = Int
k *^ T t = T $ k * t
Then your T -> Int -> T
operator is ^*
, which is simply flip (*^)
.
That leads also to the more general what you should do when overloading a standard operator with a different meaning: just make it a separate definition. You don't even need to give it a different name, this can also be disambiguated using qualified
module imports.
Just please don't instantiate classes incompletely, in particular not Num
. This just leads to php-ish confusion when somebody uses a generic function with those types, it compiles just fine but then horribly breaks at runtime when the calling code expects Num
semantics but the type fails to actually offer that.