看起来,newtype
定义只是遵循一些限制(例如,仅有一个构造函数)的data
定义,并且由于这些限制,运行时系统可以更高效地处理newtype
。对于未定义值的模式匹配处理稍有不同。
但假设Haskell只知道data
定义,而不知道newtype
:编译器是否能够自动找出给定数据定义是否遵循这些限制,并自动更高效地处理它?
我确定我缺少某些东西,一定有更深层次的原因。
看起来,newtype
定义只是遵循一些限制(例如,仅有一个构造函数)的data
定义,并且由于这些限制,运行时系统可以更高效地处理newtype
。对于未定义值的模式匹配处理稍有不同。
但假设Haskell只知道data
定义,而不知道newtype
:编译器是否能够自动找出给定数据定义是否遵循这些限制,并自动更高效地处理它?
我确定我缺少某些东西,一定有更深层次的原因。
使用 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
完全相同,但编译器保证不会让你混淆它们。
case undefined of i -> "ok"
(与数据的情况下 case undefined of D i -> "ok"
不同)。 - ScootyPuffdata T = T T
。这是一种奇怪的类型,我不记得在哪里看到过它被研究过(那是一个不同名字的博客)。但是,newtype
版本只能由undefined
来填充,而data
版本可以被无限多个值填充:undefined
、T undefined
、T (T undefined)
等等。 - luquiOrd Feet
和 Ord Cm
的实例,编译器将不会抱怨。但是,您不能有两个 Ord Double
的定义。 - Niket Kumardata 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"
不要将此函数应用于普通的CoolBool,让我们给它一个曲线球,并将其应用于未定义的变量!
ghci> helloMe undefined
"*** Exception: Prelude.undefined
newtype CoolBool = CoolBool { getCoolBool :: Bool }
ghci> helloMe undefined
"hello"
这里有另一份资料。根据这篇Newtype文章所述:
newtype声明会创建一个新类型,方法和data相差无几。 newtype的语法和用法与data声明几乎相同-实际上,您可以将newtype关键字替换为data,编译仍然可以通过,事实上,您的程序甚至可能仍然能正常工作。然而,反之不成立-data只有在类型恰好具有一个构造函数且该函数内部恰好有一个字段时才能被替换为newtype。
一些示例:
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)
请参阅 该文章 以了解混乱的细节...Sounds pretty limited! So why does anyone use newtype?
The short version The restriction to one constructor with one field means that the new type and the type of the field are in direct correspondence:
State :: (s -> (a, s)) -> State s a runState :: State s a -> (s -> (a, s))
or in mathematical terms they are isomorphic. This means that after the type is checked at compile time, at run time the two types can be treated essentially the same, without the overhead or indirection normally associated with a data constructor. So if you want to declare different type class instances for a particular type, or want to make a type abstract, you can wrap it in a newtype and it'll be considered distinct to the type-checker, but identical at runtime. You can then use all sorts of deep trickery like phantom or recursive types without worrying about GHC shuffling buckets of bytes for no reason.
对于着迷于项目符号列表的人的简化版本(找不到一个,所以只能自己写):
data - 创建具有值构造函数的新代数类型
newtype - 创建具有值构造函数的新“装饰”类型
type - 创建一个类型的替代名称(同义词)(类似于C中的typedef)
[*] 关于模式匹配惰性:
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"