Newtype是专门的数据类型声明。 这样它只包含一个构造函数和一个字段。
newtype Foo a = Bar a newtype Id = MkId Word

典型的新手问题
与数据类型数据有什么区别? data Foo a = Bar a data Id = MkId Word
新类型的主要特点是它与唯一字段包含相同的部分。 更准确地说,它在类型级别上不同于原始类型,但是在内存中具有相同的表示形式,并且它是经过严格计算的(不是延迟的)。
简而言之-新类型由于其呈现而更加有效。
是的,这对我没有任何意义...我将使用数据不,好的,最后,您始终可以为严格(而非惰性)字段启用扩展名
-funpack-strict-fields :)或直接指定
data Id = MkId !Word
尽管如此,新类型的功能
并不仅限于计算效率。 他们更强大!
3个新角色

隐藏实施
module Data.Id (Id()) where newtype Id = MkId Word
newtype与原始的(内部只是
Word)不同 。
但是我们将
MkId构造函数隐藏在模块外部。
实施实施
{-# LANGUAGE GeneralizedNewtypeDeriving #-} newtype Id = MkId Word deriving (Num, Eq)
尽管在Haskell2010标准中不是这种情况,但是通过扩展通用newTypes的输出,您可以自动推断newtype的行为与内部字段的行为相同。 在我们的案例中,
Eq Id和
Num Id的行为与
Eq Word和
Num Word相同 。
通过扩展精细育种(
DerivingVia )可以实现更多的目标,但以后会更多。
实施选择
尽管有自己的构造函数,但在某些情况下,您可以使用内部表示形式。
挑战赛
有一个整数列表。 仅需一遍即可找到最大和总计金额。
并且不要使用
foldl和
folds软件包 。
典型答案
当然,
折 ! :)
foldr :: Foldable t => (a -> b -> b) -> b -> ta -> b
并且,最终功能的描述如下:
aggregate :: [Integer] -> (Maybe Integer, Integer) aggregate = foldr (\el (m, s) -> (Just el `max` m, el + s)) (Nothing, 0)
如果仔细观察,您会在两边看到类似的操作:
只是el`max` m和
el + s 。 在两种情况下,都是映射和二进制操作。 空元素为
Nothing和
0 。
是的,这些是类人动物!
Monoid和Semigroup更详细半群是关联二进制运算的一个属性
x ⋄ (y ⋄ z) == (x ⋄ y) ⋄ z
Monoid是关联运算的属性(即半群)
x ⋄ (y ⋄ z) == (x ⋄ y) ⋄ z
它具有一个空元素,该元素不会更改右侧或左侧的任何元素
x ⋄ empty == x == empty ⋄ x
max和
(+)都是关联的,都具有空元素
-Nothing和
0 。
而且,monoid映射与卷积的组合是
可折叠的 !
可折叠更详细回想一下折叠的定义:
class Foldable t where foldMap :: (Monoid m) => (a -> m) -> ta -> m ...
让我们将折叠行为应用于
max和
(+) 。 我们最多只能组织一个
Word monoid实现。 现在该利用新类型选择的实现了!
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
有必要发表评论。
事实是,要成为数据类型
Max的单等式,我们需要一个最小元素,即要存在一个空元素。 因此,只有有限的
Max a可以是一个半身像。
理论上正确的最大元素等式 newtype Max a = Max a instance Ord a => Semigroup (Max a) instance Bounded a => Monoid (Max a)
因此,我们将不得不以某种方式转换数据类型,以便出现一个空元素,并可以使用混凝。
共轭
Maybe元素将一个半群变成一个半群!
放宽GHC最新版本中的限制早在GHC 8.2中,就需要一个monoid类型约束
instance Monoid a => Monoid (Maybe a)
这意味着我们需要另一种新类型:
而且在GHC 8.4中已经非常简单了,它只需要半组类型限制,甚至不需要创建Option类型。
instance Semigroup a => Monoid (Maybe a)
折叠反应
好了,现在使用可折叠性和箭头更新代码。
我们记得(。)只是一个功能组成:
(.) :: (b -> c) -> (a -> b) -> a -> c f . g = \x -> f (gx)
记住
fmap是一个函子:
fmap :: Functor f => (a -> b) -> fa -> fb
并且它对
Maybe的实现描述得更高。
箭头更详细箭头是某些功能的属性,使您可以在框图中使用它们。
可以在这里找到更多详细信息:
箭头:计算的通用接口在我们的例子中,我们使用箭头功能
那是
instance Arrow (->)
我们将使用以下功能:
(***) :: Arrow a => abc -> ab' c' -> a (b, b') (c, c') (&&&) :: Arrow a => abc -> abc' -> ab (c, c')
对于我们的情况
abc == (->) bc == b -> c
因此,我们功能的签名减少到:
(***) :: (b -> c) -> (b' -> c') -> ((b, b') -> (c, c')) (&&&) :: (b -> c) -> (b -> c') -> (b -> (c, c'))
或者用简单的话来说,一个函数
(***)将具有一个参数(和一个输出类型)的两个函数组合成一个函数,该函数在输入和输出分别具有一对参数,并且具有一对输出类型。
函数
(&&&)是简化版本
(***) ,其中两个函数的输入参数的类型相同,并且在输入处我们没有一对参数,而是一个参数。
总计,统一功能已获取格式:
import Data.Semigroup import Data.Monoid import Control.Arrow aggregate :: [Integer] -> (Maybe Integer, Integer) aggregate = (fmap getMax *** getSum) . (foldMap (Just . Max &&& Sum))
结果很简短!
但是,包装和反转嵌套类型的数据仍然很麻烦!
您可以进一步减少它,无资源的强制转换将为我们提供帮助!
安全,无资源的强制转换和类型角色
Unsafe.Coerce包中有一个函数
-unsafeCoerce import Unsafe.Coerce(unsafeCoerce) unsafeCoerce :: a -> b
该函数强制不安全地将类型从
a转换为
b 。
实际上,该函数是魔术,它告诉编译器将类型
a的数据
视为类型
b ,而不考虑此步骤的后果。
它可以用于转换嵌套类型,但是您需要非常小心。
2014年,发生了一场
新型革命,即安全,无资源的强制转换!
import Data.Coerce(coerce) coerce :: Coercible ab => a -> b
此功能开启了使用
newtype的新时代。
强制强制转换器适用于在存储器中具有相同结构的类型。 它看起来像类型类,但实际上GHC在编译时会转换类型,因此无法自行定义实例。
Data.Coerce.coerce函数允许您在没有资源的情况下转换类型,但是为此,我们需要访问类型构造函数。
现在简化我们的功能:
import Data.Semigroup import Data.Monoid import Control.Arrow import Data.Coerce aggregate :: [Integer] -> (Maybe Integer, Integer) aggregate = coerce . (foldMap (Just . Max &&& Sum))
我们避免了提取嵌套类型的例程;我们做到这一点时不会浪费资源,而仅使用一个函数。
嵌套数据类型的作用
使用
coerce函数
,我们可以强制转换任何嵌套类型。
但是是否有必要广泛使用此功能?
从语义上讲,从
Sorted(Down a)转换为
Sorted a是荒谬的。
但是,您可以尝试:
ghci> let h = fromList2Sorted [1,2,3] :: Sorted Int ghci> let hDown = fromList2Sorted $ fmap Down [1,2,3] :: Sorted (Down Int) ghci> minView h Just (Down 1) ghci> minView (coerce h :: Sorted (Down Int)) Just (Down 1) ghci> minView hDown Just (Down 3)
一切都会好起来的,但正确的答案是
Just(Down 3) 。
即,为了消除错误行为,引入了类型角色。
{-# LANGUAGE RoleAnnotations #-} type role Sorted nominal
让我们现在尝试:
ghci> minView (coerce h :: Sorted (Down Int)) error: Couldn't match type 'Int' with 'Down Int' arising from a use of 'coerce'
好多了!
共有3个角色(
类型角色 ):
- 代表性的 -等同于如果相同的代表性
- 标称 -必须具有完全相同的类型
- 幻像 -与真实内容无关。 等同于任何东西
在大多数情况下,编译器足够聪明,可以揭示类型的作用,但可以提供帮助。
细化派生派生行为
由于
DerivingVia语言的扩展,新类型的分发角色得到了改善。
从最近发布的GHC 8.6开始,此新扩展已经出现。
{-# LANGUAGE DerivingVia #-} newtype Id = MkId Word deriving (Semigroup, Monoid) via Max Word
如您所见,由于改进了输出方式,因此会自动推断出类型行为。
DerivingVia可以应用于支持
Coercible的任何类型,
最重要的是-完全不消耗资源!
如果
DerivingVia支持
泛型泛型和强制转换,则
DerivingVia不仅可以应用于新类型,还可以应用于任何同构类型。
结论
Types
newtype是一种强大的力量,可以极大地简化和改进代码,消除例程并减少资源消耗。
原文翻译 :新类型
的强大力量(石井宏 美)PS:我认为,在这篇文章发表于一年多以前(不是我的文章)之后, Haskell中关于新类型的新类型的魔力将变得更加清晰!