Haskell数据类型中的"Inheriting"操作



我创建了以下类型:

data Inch = Inch Double
instance Show Inch where
show (Inch i) = show i ++ " inches"

现在,我希望能够对这种类型执行一些数学运算,并且由于该类型本身基本上只是Double的同义词,因此我希望免费获得它们。但是,情况并非如此:

ghci> (3 :: Double) / 2
1.5
ghci> (3 :: Inch) / 2
<interactive>:74:2: error:
• No instance for (Num Inch) arising from the literal ‘3’
• In the first argument of ‘(/)’, namely ‘(3 :: Inch)’
In the expression: (3 :: Inch) / 2
In an equation for ‘it’: it = (3 :: Inch) / 2
<interactive>:74:13: error:
• No instance for (Fractional Inch) arising from a use of ‘/’
• In the expression: (3 :: Inch) / 2
In an equation for ‘it’: it = (3 :: Inch) / 2

我想我可以通过定义来解决这个问题:

(/) :: Inch -> Double -> Inch
(Inch i) / n = Inch (i GHC.Real./ n)

这允许前面的代码正常运行:

ghci> (Inch 3) / 2
1.5 inches

但我觉得这很麻烦,忍不住想肯定有更好的方法。 有吗?

我会推荐这个:

{-# LANGUAGE DeriveGeneric, DeriveAnyClass #-}
import GHC.Generics
import Data.VectorSpace
data Length = Inches Double
deriving (Generic, Show, AdditiveGroup, VectorSpace)

然后

ghci> Inches 3 ^/ 2
Inches 1.5

我正在使用矢量空间包中的^/运算符,因为它实际上具有合适的类型v -> Scalar v -> v.相比之下,来自Fractional类的标准/只是键入v -> v -> v,在这种情况下会Length -> Length -> Length,这在物理上没有意义。

你需要通过使编译器成为某个合适类的实例来告诉编译器Inch是一个数字。 一个选项是Num类:

> :info Num
class Num a where
(+) :: a -> a -> a
(-) :: a -> a -> a
(*) :: a -> a -> a
negate :: a -> a
abs :: a -> a
signum :: a -> a
fromInteger :: Integer -> a
{-# MINIMAL (+), (*), abs, signum, fromInteger, (negate | (-)) #-}

现在我们定义Inch. 我将使用newtype而不是data,以便我们可以使用GeneralizedNewtypeDeriving更轻松地实现Num实例:

> :set -XGeneralizedNewtypeDeriving
> newtype Inch = Inch Int deriving (Eq, Ord, Read, Show, Num)
> Inch 3 + Inch 5
Inch 8

值得注意的是,Inch 实际上不是一个数字 - 如果你尝试Inch 4 / Inch 2你会得到Inch 2,但为了正确起见,单位应该抵消,留下一个无单位的2。 但是如何处理这个问题超出了这个答案的范围,在这个答案中,我学究地使用了更老式的结构

最新更新