我使用的是
Map String (Int, Int)
,其中两个 Int
用作分子和分母,形成要传递给 Rational
的
fromList
。
然后我意识到,在我的代码中的某个点上,我以相反的方式使用了这两个
Int
(作为分母和分子,即交换)。花了一些时间才找出问题所在,所以后来我想也许我应该使用两种专用类型,所以我写了
newtype MyNum = MyNum Int
newtype MyDen = MyDen Int
但是随后我必须添加一些实例才能使其他所有内容正常工作(考虑到我对这些
Int
的用途,我必须添加 deriving (Eq, Ord, Show, Read)
),并且还要添加一些两个函数来打开 Int
s 来自两种类型,这样我实际上可以将 (+1)
之类的东西应用到那些包裹的 Int
s 上。
但这意味着代码开始看起来有点难看,像
(MyNum . (+1) . unwrapMyNum)
这样的东西,而像 (+1) <$>
这样的东西会更好。
但这意味着
MyNum
应该是 Functor
;但它不能,因为它是硬类型,而不是类型构造函数。
但我不想将其设为类型构造函数,因为我不想在其中包含除
Int
之外的任何内容。
有什么建议吗?
我认为实际问题与你的具体问题无关。只是不要使用元组,使用合适的类型来表达两个整数一起代表的内容。在这种情况下,显而易见的选择是使用
Ratio Int
,但需要注意的是它不存储任意对,而是正确标准化分数(这通常是一件好事)。如果这不适合您,只需编写您自己的 Ratio
类型。
也就是说,您还可以做很多事情来使单一类型的新类型包装器更方便:
派生实例。看来您仍在包装类型上使用数值运算。这很容易启用:
{-# LANGUAGE DerivingStrategies #-}
newtype MyNum = MyNum Int
deriving stock (Eq, Ord, Show, Read)
deriving newtype (Num, Enum, Real, Integral)
而现在
MyNum
基本上是Int
的全功能克隆,你可以直接为negate (n + 9)
编写n :: MyNum
等表达式,无需包装和展开。 (但是当您将 MyNum
传递给需要 MyDen
的内容时,编译器仍然会犹豫不决。)
单函子。如果您想强调
MyNum
是 Int
的容器,而不是 本身就是一种不同类型的数字类型,那么可以使用 MonoFunctor
类。你可以实例化
{-# LANGUAGE TypeFamilies #-}
type instance Element MyNum = Int
instance MonoFunctor MyNum where
omap f (MyNum i) = MyNum (f i)
然后写例如
omap (+1) n
,类似于
(+1)<$>n
,但不需要容器支持除 Int
之外的任何内容。另请查看该包的其他类。
和lenses(更具体地说是
Iso
s)。