Haskell “为什么会有?”;“数据”;及;“新类型”;在哈斯克尔?

Haskell “为什么会有?”;“数据”;及;“新类型”;在哈斯克尔?,haskell,types,language-design,type-systems,Haskell,Types,Language Design,Type Systems,似乎newtype定义只是一个遵守某些限制(例如,只有一个构造函数)的data定义,并且由于这些限制,运行时系统可以更有效地处理newtypes。对于未定义值的模式匹配处理略有不同 但是假设Haskell只知道数据定义,没有newtypes:编译器不能自己找出给定的数据定义是否遵守这些限制,并自动更有效地处理它吗 我肯定我错过了什么,一定有更深层次的原因;数据声明在访问和存储其“成员”时使用惰性计算,而newtype则不使用。Newtype还从其组件中剥离所有以前的类型实例,有效地隐藏其实现;而

似乎
newtype
定义只是一个遵守某些限制(例如,只有一个构造函数)的
data
定义,并且由于这些限制,运行时系统可以更有效地处理
newtype
s。对于未定义值的模式匹配处理略有不同

但是假设Haskell只知道
数据
定义,没有
newtype
s:编译器不能自己找出给定的数据定义是否遵守这些限制,并自动更有效地处理它吗


我肯定我错过了什么,一定有更深层次的原因;数据声明在访问和存储其“成员”时使用惰性计算,而newtype则不使用。Newtype还从其组件中剥离所有以前的类型实例,有效地隐藏其实现;而数据使实现保持开放状态

在避免复杂数据类型中的样板代码时,我倾向于使用newtype,因为在复杂数据类型中,我不一定需要访问内部代码。这将加快编译和执行速度,并降低使用新类型时的代码复杂性


当我第一次读到这篇文章时,我发现对Haskell的温和介绍相当直观。

这两个
newtype
和单构造函数
data
都引入了单值构造函数,但是
newtype
引入的值构造函数是严格的,而
data
引入的值构造函数是惰性的。所以如果你有

data D = D Int
newtype N = N Int
然后
N undefined
相当于
undefined
,并在计算时导致错误。但是
D undefined
并不等同于
undefined
,只要你不想偷看里面,就可以对它进行评估

编译器无法自行处理此问题

不,不完全是这样。在这种情况下,作为程序员,您可以决定构造函数是严格的还是懒惰的。要理解何时以及如何使构造函数严格或懒惰,您必须比我更好地理解懒惰计算。我坚持报告中的观点,即
newtype
可用于重命名现有类型,例如具有几种不同的不兼容测量类型:

newtype Feet = Feet Double
newtype Cm   = Cm   Double
两者在运行时的行为完全类似于Double,但编译器承诺不会让您混淆它们。

根据:

使用newtype关键字代替data关键字。为什么会这样 那个首先,新类型更快。如果使用data关键字 包装一个类型,所有的包装和展开都会有一些开销 当程序运行时。但是如果你使用newtype,Haskell知道 您只是使用它将现有类型包装为新类型 (因此得名),因为您希望它内部相同,但 有一个不同的类型。考虑到这一点,Haskell可以摆脱 解析哪一个值属于哪一类型后,将进行包装和展开

那么为什么不一直使用newtype而不是data呢?好 使用newtype从现有类型创建新类型时 关键字,只能有一个值构造函数和该值 构造函数只能有一个字段。但有了数据,你就可以制造数据 具有多个值构造函数且每个构造函数都可以 具有零个或多个字段:

当使用newtype时,只能使用一个构造函数和一个 场

现在考虑以下类型:

它是用定义的普通代数数据类型 数据关键字。它有一个值构造函数,它有一个字段 谁的类型是布尔。让我们创建一个模式与 CoolBool并返回值“hello”,无论Bool 在CoolBool里面是真是假:

与其将此函数应用于普通CoolBool,不如将其抛出curveball并应用于undefined

哎呀!例外!为什么会发生这种异常?定义的类型 使用data关键字可以有多个值构造函数(甚至 虽然CoolBool只有一个)。因此,为了查看给定的值 为了使我们的函数符合(CoolBool)模式,Haskell必须 对值进行足够的求值,以查看使用了哪个值构造函数 当我们创造价值的时候。当我们试图计算一个未定义的 值,即使是一点点,也会引发异常

让我们尝试使用 新类型:

我们不必这么做 更改helloMe函数,因为模式匹配语法是 如果使用newtype或data定义类型,则相同。让我们开始吧 在这里使用相同的方法,并将helloMe应用于未定义的值:

成功了!嗯,为什么?正如我们所说,当我们使用 newtype,Haskell可以在内部表示新类型的值 以与原始值相同的方式。它不需要再添加一个 在它们周围的框中,它只需要知道它们的值 不同类型。因为Haskell知道用 newtype关键字只能有一个构造函数,不必这样 计算传递给函数的值,以确保 符合(CoolBool)模式,因为newtype类型只能 有一个可能的值构造函数和一个字段

这种行为上的差异可能看起来微不足道,但实际上是很好的 这很重要,因为它帮助我们认识到即使定义了类型 从程序员的角度来看,data和newtype的行为类似 视图,因为它们都有值构造函数和字段,所以 实际上有两种不同的机制。而数据可以用来制作 你自己的类型从头开始,newtype是用来制作一个全新的 从现有类型中键入。基于新类型值的模式匹配
data Profession = Fighter | Archer | Accountant  

data Race = Human | Elf | Orc | Goblin  

data PlayerCharacter = PlayerCharacter Race Profession 
data CoolBool = CoolBool { getCoolBool :: Bool } 
helloMe :: CoolBool -> String  
helloMe (CoolBool _) = "hello"  
ghci> helloMe undefined  
"*** Exception: Prelude.undefined  
newtype CoolBool = CoolBool { getCoolBool :: Bool }   
ghci> helloMe undefined  
"hello"
newtype Fd = Fd CInt
-- data Fd = Fd CInt would also be valid

-- newtypes can have deriving clauses just like normal types
newtype Identity a = Identity a
  deriving (Eq, Ord, Read, Show)

-- record syntax is still allowed, but only for one field
newtype State s a = State { runState :: s -> (s, a) }

-- this is *not* allowed:
-- newtype Pair a b = Pair { pairFst :: a, pairSnd :: b }
-- but this is:
data Pair a b = Pair { pairFst :: a, pairSnd :: b }
-- and so is this:
newtype Pair' a b = Pair' (a, b)
State :: (s -> (a, s)) -> State s a
runState :: State s a -> (s -> (a, s))
data DataBox a = DataBox Int
newtype NewtypeBox a = NewtypeBox Int

dataMatcher :: DataBox -> String
dataMatcher (DataBox _) = "data"

newtypeMatcher :: NewtypeBox -> String 
newtypeMatcher (NewtypeBox _) = "newtype"

ghci> dataMatcher undefined
"*** Exception: Prelude.undefined

ghci> newtypeMatcher undefined
“newtype"