Enr*_*lis 3 haskell functional-programming operator-overloading typeclass primitive-types
假设我有一个包含现有类型的自定义类型,
newtype T = T Int deriving Show
Run Code Online (Sandbox Code Playgroud)
并假设我希望能够将Ts 相加,并且将它们相加应该会导致将包装的值相加;我会通过
instance Num T where
(T t1) + (T t2) = T (t1 + t2)
-- all other Num's methods = undefined
Run Code Online (Sandbox Code Playgroud)
我认为我们到目前为止都很好。请告诉我到目前为止是否存在重大问题。
现在让我们假设我希望能够将 a 乘以TanInt并且结果应该是 a,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
Run Code Online (Sandbox Code Playgroud)
这显然不起作用,因为class Numdeclares (*) :: a -> a -> a,因此要求两个操作数(和结果)都是相同的类型。
即使定义(*)为自由函数也会带来类似的问题(即(*)已经存在于 中Prelude)。
我怎么能处理这个?
至于这个问题的原因,我可以设置以下
(Int,Int)用于笛卡尔平面中的二维向量,(Int,Int)在另一个不相关的事情上,newtype对其中至少一个使用 a 来消除两者之间的歧义,或者,如果(Int,Int)出于其他几个原因使用,那么为什么不将所有这些都newtype打包(Int,Int)?newtype Vec2D = Vec2D (Int,Int)在平原中代表一个向量,所以能够做到Vec2D (2,3) * 4 == Vec2D (8,12).非常相似的例子已经经常被问到,答案是这不是一个数字类型,因此不应该有一个Num实例。它实际上是一个向量空间类型,因此你应该定义
{-# 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
Run Code Online (Sandbox Code Playgroud)
那么您的T -> Int -> T运营商是^*,这很简单flip (*^)。
这也导致了在重载具有不同含义的标准运算符时应该做的更一般的事情:只需将其作为单独的定义即可。你甚至不需要给它一个不同的名字,这也可以使用qualified模块导入。
只是请不要不完整地实例化类,特别是不要Num。当有人使用具有这些类型的泛型函数时,这只会导致 php-ish 混淆,它编译得很好,但是当调用代码期望Num语义但类型实际上无法提供语义时,它会在运行时可怕地中断。