在用户定义的类型和现有的类型之间定义现有的(例如Prelude)运算符的正确方法是什么? [英] What is the correct way to define an already existing (e.g. in Prelude) operator between a user-defined type and an existing type?

查看:40
本文介绍了在用户定义的类型和现有的类型之间定义现有的(例如Prelude)运算符的正确方法是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有一个包装现有类型的自定义类型,

Suppose I have a custom type wrapping an existing type,

newtype T = T Int deriving Show

并假设我希望能够添加 T ,并且将它们相加应导致将包装后的值相加;我可以通过

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.

现在让我们假设我希望能够将 T Int 相乘,并且结果应该是一个 T ,其包装值是前者乘以int;我会选择这样的东西:

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

这显然不起作用,因为 class Num 声明了(*):: a->a->,因此要求两个操作数(以及结果)必须是同一类型.

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.

即使将(*)定义为自由函数也会引起类似的问题(即 Prelude 中已经存在(*)).

Even defining (*) as a free function poses a similar problem (i.e. (*) exists already in Prelude).

我该如何处理?

关于这个问题的原因,我可以使用以下设备

As for the why of this question, I can device the following

  • 在我的程序中,我想对笛卡尔平面中的2D向量使用(Int,Int)
  • 但我也将(Int,Int)用于另一不相关的事物,
  • 因此,我必须对两者进行歧义,至少要对其中之一使用 newtype ,或者,如果出于其他一些原因使用(Int,Int),那么为什么不将它们全部包装成(Int,Int)?
  • 由于 newtype Vec2D = Vec2D(Int,Int)表示纯矢量,因此能够执行 Vec2D(2,3)* 4 == Vec2D(8,12).
  • 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).

推荐答案

已经经常问过非常类似的例子,答案是这不是不是数字类型,因此不应有数字 Num 实例.它实际上是一个向量空间类型,因此应改为定义

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

然后您的 T->整数->T 运算符为这也使您在更普通的意义上重载具有不同含义的标准运算符时应该执行的操作:只需将其设置为单独的定义即可.您甚至不需要给它起一个不同的名称,也可以使用 qualified 模块导入来消除此歧义.

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.

请不要实例化不完整的类,尤其是不要 .当有人使用具有这些类型的泛型函数时,这只会引起php-ish混乱,它编译得很好,但是当调用代码期望 Num 语义时却在运行时可怕地中断了,但该类型无法实际提供该功能

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.

这篇关于在用户定义的类型和现有的类型之间定义现有的(例如Prelude)运算符的正确方法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆