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

我与影子孤独终老i 提交于 2021-02-09 09:22:49

问题


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).

回答1:


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.



来源:https://stackoverflow.com/questions/65641353/what-is-the-correct-way-to-define-an-already-existing-e-g-in-prelude-operator

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!