假设我有一个包装现有类型的自定义类型,
newtype T = T Int deriving Show
假设我希望能够加起来T
s,并且将它们相加应该会导致将包装值相加;我会通过以下方式做到这一点
instance Num T where
(T t1) + (T t2) = T (t1 + t2)
-- all other Num's methods = undefined
我认为到目前为止我们都很好。请告诉我到目前为止是否存在重大问题。
现在假设我想要能够乘以T
by an Int
结果应该是T
其包装值是前者乘以 int;我会去做这样的事情:
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 -> a
,因此要求两个操作数(和结果)都是相同的类型。
甚至定义(*)
作为一个自由函数会带来类似的问题(即(*)
已经存在于Prelude
).
我该如何处理这个问题?
至于为什么会出现这个问题,我可以推测如下
- 在我的程序中我想使用
(Int,Int)
对于笛卡尔平面中的 2D 向量,
- 但我也用
(Int,Int)
对于另一件不相关的事情,
- 因此我必须通过使用来消除两者之间的歧义
newtype
至少其中之一,或者,如果使用(Int,Int)
由于其他几个原因,那么为什么不全部制作newtype
包装(Int,Int)
?
- since
newtype Vec2D = Vec2D (Int,Int)
表示平原中的向量,能够做到这一点是有意义的Vec2D (2,3) * 4 == Vec2D (8,12)
.
非常相似的例子已经经常被问到,答案是这是not数字类型,因此不应有Num
实例。它实际上是一个向量空间 http://hackage.haskell.org/package/vector-space类型,相应地你应该定义
{-# 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 -> Int -> T
运算符是^* http://hackage.haskell.org/package/vector-space-0.16/docs/Data-VectorSpace.html#v:-94--42-,这就是简单的flip (*^)
.
这也导致了在重载具有不同含义的标准运算符时应该做的更一般的事情:只需将其设为separate定义。您甚至不需要给它一个不同的名称,这也可以使用来消除歧义qualified
模块导入。
只是请不要不完整地实例化类,特别是Num
。当有人使用这些类型的泛型函数时,这只会导致 php 式的混乱,它编译得很好,但当调用代码期望时,它会在运行时可怕地中断Num
语义,但类型实际上未能提供该语义。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)