即使你不能对它们进行模式匹配,新类型也不会产生费用吗?

Are*_* Fu 2 haskell newtype

上下文

我所知道的大多数Haskell教程(例如LYAH)都引入了newtypes作为一种免费的成语,可以实现更多类型的安全性.例如,此代码将进行类型检查:

type Speed = Double
type Length = Double

computeTime :: Speed -> Length -> Double
computeTime v l = l / v
Run Code Online (Sandbox Code Playgroud)

但这不会:

newtype Speed = Speed { getSpeed :: Double }
newtype Length = Length { getLength :: Double }

-- wrong!
computeTime :: Speed -> Length -> Double
computeTime v l = l / v
Run Code Online (Sandbox Code Playgroud)

这将:

-- right
computeTime :: Speed -> Length -> Double
computeTime (Speed v) (Length l) = l / v
Run Code Online (Sandbox Code Playgroud)

在这个特定的例子中,编译器知道它Speed只是一个Double,因此模式匹配没有实际意义,并且不会生成任何可执行代码.

当newtypes作为参数类型的参数出现时,它们是否仍然没有成本?例如,考虑一个newtypes列表:

computeTimes :: [Speed] -> Length -> [Double]
computeTimes vs l = map (\v -> getSpeed v / l) vs
Run Code Online (Sandbox Code Playgroud)

我也可以在lambda中模式匹配速度:

computeTimes' :: [Speed] -> Length -> [Double]
computeTimes' vs l = map (\(Speed v) -> v / l) vs
Run Code Online (Sandbox Code Playgroud)

在任何一种情况下,由于某种原因,我觉得真正的工作正在完成!当newtype被埋没在嵌套参数数据类型的深层树中时,我开始感到更加不舒服,例如Map Speed [Set Speed]; 在这种情况下,可能很难或不可能对newtype进行模式匹配,并且必须使用类似的访问器getSpeed.

TL; DR

即使newtype看起来像另一个参数类型的(可能是深埋的)参数,使用newtype 也不会产生成本吗?

chi*_*chi 6

靠自己,newtypes没有成本.在它们上应用它们的构造函数或模式匹配没有成本.

当作为参数用于其它类型的例如[T]的表示[T]是精确地相同的一个用于[T']如果Tnewtype forT`.所以,性能没有任何损失.

但是,我可以看到两个主要的警告.

newtype和实例

首先,newtype经常用于引入新instance类型的类.显然,当这些是用户定义的时,不能保证它们与原始实例具有相同的成本.例如,使用时

newtype Op a = Op a
instance Ord a => Ord (Op a) where
    compare (Op x) (Op y) = compare y x
Run Code Online (Sandbox Code Playgroud)

比较两个Op Int将比比较略高Int,因为参数需要交换.(我在这里忽略了优化,当它们触发时可能会使这个成本免费.)

newtypes 用作类型参数

第二点更微妙.考虑以下两种身份实现[Int] -> [Int]

id1, id2 :: [Int] -> [Int]
id1 xs = xs
id2 xs = map (\x->x) xs
Run Code Online (Sandbox Code Playgroud)

第一个有不变的成本.第二个具有线性成本(假设没有优化触发器).一个聪明的程序员应该更喜欢第一个实现,它也更容易编写.

假设现在我们只介绍newtypes参数类型:

id1, id2 :: [Op Int] -> [Int]
id1 xs = xs                    -- error!
id2 xs = map (\(Op x)->x) xs
Run Code Online (Sandbox Code Playgroud)

由于类型错误,我们无法再使用常量成本实现.线性成本实施仍然有效,并且是唯一的选择.

现在,这非常糟糕.输入表示[Op Int]是完全一点一点的,相同的[Int].然而,类型系统禁止我们以有效的方式执行身份!

为了克服这个问题,在Haskell中引入了安全强制.

id3 :: [Op Int] -> [Int]
id3 = coerce
Run Code Online (Sandbox Code Playgroud)

coerce在某些假设下,魔术函数会newtype根据需要移除或插入s以进行类型匹配,即使其他类型中也是如此,[Op Int]如上所述.此外,它是零成本功能.

请注意,coerce仅在某些条件下有效(编译器会检查它们).其中之一是newtype构造函数必须是可见的:如果模块不导出Op :: a -> Op a,则不能强制Op Int转换Int,反之亦然.实际上,如果模块导出类型但不导出构造函数,那么无论如何都要使构造函数可访问coerce.这使得"智能构造函数"成语仍然安全:模块仍然可以通过不透明类型强制执行复杂的不变量.