仅支持加法和减法的Haskell类型
假设我有一个这样定义的类型仅支持加法和减法的Haskell类型,haskell,time,timer,Haskell,Time,Timer,假设我有一个这样定义的类型 data Seconds | Seconds Integer 我可以像这样定义一个倒计时函数 decrementTimer :: Seconds -> Seconds -> Seconds decrementTimer (Seconds internalSecondsOne) (Seconds internalSecondsTwo) = Seconds $ internalSecondsOne - internalSecondsTwo decremen
data Seconds | Seconds Integer
我可以像这样定义一个倒计时函数
decrementTimer :: Seconds -> Seconds -> Seconds
decrementTimer (Seconds internalSecondsOne) (Seconds internalSecondsTwo) = Seconds $ internalSecondsOne - internalSecondsTwo
decrementTimer :: Seconds -> Seconds -> Seconds
decrementTimer a b = a - b
但这似乎是乏味和混乱的,我必须为每一个时间的代表做这件事;小时、分钟、时间段保存秒、分钟和小时的数据
我真正想做的是“实现”(?)Num类型的类,所以我可以做类似的事情
decrementTimer :: Seconds -> Seconds -> Seconds
decrementTimer (Seconds internalSecondsOne) (Seconds internalSecondsTwo) = Seconds $ internalSecondsOne - internalSecondsTwo
decrementTimer :: Seconds -> Seconds -> Seconds
decrementTimer a b = a - b
但是我不需要支持乘法和除法吗?把秒除以秒是没有意义的。我如何制作一个支持加减法的类型?或者,如果这是不可能的,或者我的推理完全错误,那么在Haskell中实现这一点的惯用方法是什么?如果要限制对数据类型的操作,标准技巧是不导出类型的构造函数。这样函数就不能访问数据的内部,只能使用您提供的操作。所以你想要的是:
module Seconds (Seconds) where
newtype Seconds = Seconds Integer
mkSeconds :: Integer -> Seconds
addSeconds :: Seconds -> Seconds -> Seconds
subSeconds :: Seconds -> Seconds -> Seconds
请注意,模块导出的是秒
,而不是秒(..)
,因此类型秒
可用,但构造函数不可用。现在不可能编写函数了
dangerousMult :: Seconds -> Seconds -> Seconds
dangerousMult (Seconds i) (Seconds j) = Seconds (i * j)
如果使用标准prelude,您可能会遇到运气不佳的情况,Num typeclass要求您实现对该数据类型没有意义的函数。基本上有三种选择
+
和-
的函数在秒数上工作,但是如果不将秒数作为Num
的实例,它就无法与Num
类型类中的+
和-
相同(因此,这将导致任何获得Seconds
值作为通用Num a
的代码也可以使用其他Num
函数)
您所要做的就是显式导入前奏曲,要么隐藏+
和-
要么导入它
问题是,任何使用+
和-
的代码也必须采取措施来解决前奏+
和-
的歧义;要么范围内只有一个版本的+
,要么至少其中一个必须始终使用限定名称引用(一些Prelude.++
,P.++
,S.++
,Seconds.++
,等等的变体)。对于一个晦涩的名字,这有时是可以接受的。对于像+
这样常见和基本的东西,这可能不是一个好主意
您可以通过在一个新类型类(比如PlusMinus
)中生成+
和-
函数,并编写instance Num a=>PlusMinus a,其中(+)=(Prelude.+)
等,使该选项变得更好。然后,您还可以将Seconds
作为PlusMinus
的实例
这给您带来的好处是,任何想要使用新的+
操作符的代码至少可以安全地隐藏Prelude的+
,同时仍然能够在其他Num
类型上使用+
。不过,它仍然会给想要使用+
的每个模块带来一些麻烦,而且它有可能被配置使用(有一天,某人可能会看到+
在秒
上被使用,而对所有这些都不太熟悉,并假设他们可以在秒
上使用其他数值操作)
最好是生成不被调用的函数+
和-
。如果需要,可以使用包含+
和-
的新多字符运算符(尽管要找到其他库不使用的运算符可能会比较困难)
这是我曾经采取的一种方法,有点过分,但也有点令人满意
问题是我有代表绝对位置的向量,也有代表偏移量的向量。我认为加和减偏移量是有意义的,但不是位置。然而,在位置上加一个偏移量得到一个位置,或者减去两个位置得到一个偏移量,甚至用一个标量t乘以一个偏移量也是有意义的o获得补偿
我最后做的是定义一个类型类,如下所示:
{-# LANGUAGE MultiParamTypeClasses, TypeFamilies #-}
class Addable a b where
type Result a
(|+|) :: a -> b -> Result a b
instance Addable Offset Offset where
type Result Offset Offset = Offset
o |+| o = ...
instance Addable Position Offset where
type Result Position Offset = Position
p |+| o = ...
instance Addable Offset Position where
type Result Offset Position = Position
o |+| p = p |+| o
等
因此,你最终使用了|+|
而不是++
,但它看起来仍然有点像你习惯于思考的代数(一旦你习惯了|+|
是+
的“通用”版本的惯例,等等),它允许您对类型系统中哪些操作有意义的许多规则进行编码,以便编译器可以为您检查。缺点是大量的样板文件定义了所有实例,但对于少量固定数量的类型,您只需执行一次
1您需要扩展来实现这一点;原则上这有点不安全,因为在某个地方可能存在Num
的Seconds
实例,这将使Seconds
以两种不同的方式匹配PlusMinus
。首先–为什么不使用现有的物理量–库?例如,。将自己的时间限制在秒是很奇怪的,因为秒实际上只是许多可能的时间单位中的一个,尽管您使用的是Integer
而不是更明显的Double
这一事实表明您确实对固定时间-r感兴趣