如果类型安全是唯一的动机,那么将 Int (不是一般类型)包装在另一种类型中的正确方法是什么?

问题描述 投票:0回答:1

我使用的是

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
之外的任何内容。

有什么建议吗?

haskell functional-programming type-safety newtype
1个回答
0
投票

我认为实际问题与你的具体问题无关。只是不要使用元组,使用合适的类型来表达两个整数一起代表的内容。在这种情况下,显而易见的选择是使用

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
    之外的任何内容。另请查看该包的其他类。
    
    

  • 一般包装/展开助手。有一些替代方法可以显式操作新类型或其他包含的数据,这些方法在特殊类不适合时也可以工作,并且比传统的访问器和构造函数对更方便。其中包括
  • coerce

    lenses(更具体地说是 Isos)。
    
    

© www.soinside.com 2019 - 2024. All rights reserved.