Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
216 views
in Technique[技术] by (71.8m points)

haskell - What is the correct way to define an already existing (e.g. in Prelude) operator between a user-defined type and an existing type?

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 Ts, 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

  • in my program I want to use (Int,Int) for 2D vectors in a cartesian plane,
  • but I also use (Int,Int) for another unrelated thing,
  • therefore I have to disambiguate between the two, by using a newtype for at least one of them or, if use (Int,Int) for several other reasons, then why not making all of them newtypes wrapping (Int,Int)?
  • since 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).
question from:https://stackoverflow.com/questions/65641353/what-is-the-correct-way-to-define-an-already-existing-e-g-in-prelude-operator

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...