微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在用户定义的类型和现有类型之间定义已经存在的例如在 Prelude 中运算符的正确方法是什么?

如何解决在用户定义的类型和现有类型之间定义已经存在的例如在 Prelude 中运算符的正确方法是什么?

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

newtype T = T Int deriving Show

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

做到这一点
instance Num T where
  (T t1) + (T t2) = T (t1 + t2)
  -- all other Num's methods = undefined

我认为到目前为止我们都很好。请告诉我到目前为止是否存在重大问题。

现在让我们假设我希望能够将 T 乘以 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) 用于笛卡尔平面中的二维向量,
  • 但我也将 (Int,Int) 用于其他不相关的事情,
  • 因此我必须通过对其中至少一个使用 newtype 来消除两者之间的歧义,或者,如果出于其他几个原因使用 (Int,Int),那么为什么不让所有这些 newtype }} 包裹 (Int,Int)
  • 由于 newtype Vec2D = Vec2D (Int,Int) 代表平原中的向量,因此能够执行 Vec2D (2,3) * 4 == Vec2D (8,12) 是有意义的。

解决方法

非常相似的例子已经经常被问到,答案是这不是一个数字类型,因此不应该有一个 Num 实例。它实际上是一个 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 运算符是 ^*,也就是简单的 flip (*^)

这也导致了在重载具有不同含义的标准运算符时您应该做的更一般的事情:只需将其设为单独定义即可。您甚至不需要给它一个不同的名称,这也可以使用 qualified 模块导入来消除歧义。

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。