()在Haskell中究竟是什么?
我正在阅读学习你一个Haskell ,在monad章节中,在我看来, ()
被视为每种types的“null”。 当我检查GHCi中的()
时,我得到了
>> :t () () :: ()
这是一个非常令人困惑的说法。 看来, ()
是一个types本身。 我很困惑它如何适应这种语言,以及它如何能够代表任何types。
tl; dr ()
不会为每个types添加一个“null”值,地狱no; ()
在它自己的types()
是一个“无用的”值。
让我暂且退一步,解决一个常见的混淆之处。 学习Haskell时要注意的一个关键是区分它的expression语言和它的types语言。 你可能知道两者是分开的。 但是,这允许在这两个中使用相同的符号,这就是发生在这里。 有简单的文字提示告诉你你在看哪种语言。 你不需要parsing整个语言来检测这些线索。
Haskell模块的顶级默认情况下处于expression式语言中。 通过在expression式之间写入方程来定义函数。 但是当你在expression式语言中看到foo :: bar时,这意味着foo是一个expression式, bar是它的types。 所以当你读取() :: ()
,你会看到一个语句,它把expression式语言中的()
和types语言中的()
起来。 这两个符号意味着不同的东西,因为它们不是相同的语言。 这种重复往往会引起初学者的困惑,直到expression式/types语言分离装置在他们的潜意识中,在这一点上它变得有助于助记符。
关键字data
引入了一个新的数据types声明,涉及expression式和types语言的仔细混合,因为它首先说什么是新types,其次是它的值是什么。
数据TyCon tyvar ... tyvar = ValCon1types... type | ... | ValConntypes...types
在这样的声明中,types构造函数TyCon被添加到types语言中, ValCon值构造函数被添加到expression式语言(及其模式子语言)中。 在一个data
声明中,在ValCon的参数位置上的东西告诉你当在expression式中使用ValCon时赋予参数的types。 例如,
data Tree a = Leaf | Node (Tree a) a (Tree a)
声明一个types构造器Tree
的二叉树types存储在节点的元素,其值由值构造函数Leaf
和Node
。 我喜欢颜色types构造函数(树)蓝色和值构造函数(叶,节点)红色。 在expression式中不应该有蓝色,除非你使用高级特性,否则不能使用红色。 内置的Bool
types可以被声明,
data Bool = True | False
将蓝色Bool
添加到types语言中,并将红色True
和False
到expression式语言中。 不幸的是,我的markdown-fu不足以将颜色添加到这篇文章中,所以你只需要学习在你的头上添加颜色。
“单位”types使用()
作为一个特殊的符号,但它的工作就像宣布
data () = () -- the left () is blue; the right () is red
意思是一个名义上的blue ()
是types语言中的一个types构造函数,但是一个概念上的red ()
是expression式语言中的一个值构造函数,并且的确是() :: ()
。 [这不是这种双关语的唯一例子。 更大的元组的types遵循相同的模式:对语法就像是给定的
data (a, b) = (a, b)
将(,)添加到types和expression式语言。 但是我离题了。
所以type ()
,通常发音为“Unit”,是一个包含一个值的types值得说的:该值被写入()
但在expression式语言中,有时发音为“void”。 只有一个值的types不是很有趣。 types()
值贡献零位的信息:你已经知道它必须是什么。 所以,虽然type ()
没有什么特别的地方可以表示副作用,但它通常显示为monadictypes的值分量。 一元操作往往具有类似的forms
val-in-type-1 - > ... - > val-in-type-n - > effect-monad val-out-type
返回types是一个types的应用程序:函数告诉你哪些效果是可能的,参数告诉你该操作产生了什么types的值。 例如
put :: s -> State s ()
这是阅读(因为应用程序关联到左侧[“我们都是在六十年代”,罗杰Hindley])
put :: s -> (State s) ()
有一个值inputtypess
,effect-monad State s
和值输出types()
。 当你看到()
作为一个值输出types时,这只意味着“这个操作只用于它的效果 ;所提供的值是无趣的”。 同样
putStr :: String -> IO ()
提供一个stringstdout
但不会返回任何令人兴奋的东西。
()
types也可用作类容器结构的元素types,它表示数据只包含一个形状 ,没有有趣的有效载荷。 例如,如果Tree
如上所述,则Tree ()
是二叉树形状的types,在节点处不存储任何感兴趣的东西。 类似[()]
是无聊元素列表的types,如果列表元素中没有任何兴趣,那么它贡献的唯一信息是它的长度。
总而言之, ()
是一种types。 它的一个值()
正好具有相同的名称,但没关系,因为types和expression式语言是分开的。 有一个代表“没有信息”的types是有用的,因为在上下文中(例如,monad或容器),它告诉你只有上下文是有趣的。
()
types可以被认为是一个零元组元组。 这是一种只能有一个值的types,因此在需要types的地方使用,但实际上并不需要传达任何信息。 这有几个用途。
像IO
和State
这样的一元事物具有回报价值,也有副作用。 有时候,操作的唯一要点是执行副作用,如写入屏幕或存储某种状态。 为了写入屏幕, putStrLn
必须有typesString -> IO ?
IO
总是有一些返回types,但这里没有什么有用的返回。 那么我们应该回报什么types? 我们可以说诠释,并总是返回0,但这是误导。 所以我们返回()
,只有一个值的types(因此没有有用的信息),表示没有什么有用的回来。
有一个types可能没有任何有用的价值是有用的。 考虑一下,如果你已经实现了一个Map kv
types的Map kv
映射k
types的键值typesv
。 然后你想要实现一个Set
,除了你不需要数值部分,只需要键,就可以实现一个类似于map的Set
。 在像Java这样的语言中,你可能会使用布尔值作为虚拟值types,但实际上你只是想要一个没有有用值的types。 所以你可以说type Set k = Map k ()
应该指出的是()
并不是特别的魔法。 如果你想要的话,你可以将它存储在一个variables中,并在其上进行模式匹配(虽然没有多less意义):
main = do x <- putStrLn "Hello" case x of () -> putStrLn "The only value..."
它被称为Unit
types,通常用来表示副作用。 你可以隐约地把它想成Java中的Void
。 在这里和这里阅读更多。什么可以混淆是()
语法表示types和它的唯一值字面值。 另外请注意,它不类似于Java中的null
,这意味着一个未定义的引用 – ()
实际上是一个0大小的元组。
我真的很喜欢用元组来比喻()
。
(Int, Char)
是Int
和Char
的所有对的types,所以它的值是与所有可能的Char
相交的Int
所有可能值。 (Int, Char, String)
类似于(Int, Char, String)
的所有三元组的types。
很容易看到如何继续向上延伸这种模式,但是向下呢?
(Int)
将是“一元组”types,由Int
的所有可能值组成。 但是Haskell会把这个解释为只是把Int
括在括号内,因此就是Int
types。 在这种types的值将是(1)
, (2)
, (3)
等,这也只是被parsing为括号中的普通的Int
值。 但是如果你仔细想想,一个“一元组”就和一个单一的值完全一样,所以没有必要实际存在它们。
向下进一步到零元组给我们()
,这应该是在空的types列表中的所有可能的值组合。 那么,只有一种方法可以做到这一点,即不包含其他值,所以type ()
只应该有一个值。 通过与元组值的语法类比,我们可以将该值写为()
,它看起来像一个不包含值的元组。
这正是它的工作原理。 没有什么魔法,这种types()
及其值()
并不是由语言特别处理的。
()
实际上在LYAH书籍的monad例子中并不被视为“任何types的空值”。 每当使用type ()
, 唯一可以返回的值是()
。 所以它被用作一个types来明确地说, 不能有任何其他的返回值。 同样地,其他types应该返回,你不能返回()
。
需要注意的是,当一堆monadic计算与>>=
, >>
等的块或运算符一起组合时,它们将为某些monad m
构build一个types为ma
的值。 m
select必须在整个组件部分保持不变(这样就没有办法用IO Int
来组成一个Maybe Int
),但是在每个阶段, a
和can经常是不同的。
所以当有人在IO String
计算的中间插入一个IO ()
时,并不是使用()
作为String
types的空值,而是简单地在构buildIO String
的方式上使用 IO ()
你可以使用一个Int
来build立一个String
。
混淆来自其他编程语言:“无效”意味着在大多数命令式语言中,存储器中没有存储结构的结构。 它似乎不一致,因为“布尔”有2个值,而不是2位,而“无效”没有位,而不是没有值,但这是关于实际意义上的函数返回。 准确地说:它的单个值不消耗任何存储空间。
让我们暂时忽略价值底部(书面_|_
)…
()
被称为Unit,写成一个空元组。 它只有一个价值。 而且它不被称为Void
,因为Void
甚至没有任何值,因此不能被任何函数返回。
观察这个: Bool
有2个值( True
和False
), ()
有一个值( ()
),而Void
没有值(它不存在)。 他们就像有两个/一个/没有元素的集合。 他们需要存储他们的价值最less的内存分别是1位/无位/不可能。 这意味着一个函数返回一个()
可能会返回一个结果值(显而易见的),这可能是无用的。 另一方面, Void
意味着这个函数永远不会返回,也不会给你任何结果,因为不会有任何结果。
如果你想给“那个值”一个名字,那么一个函数返回它永远不会返回(是的,这听起来像crazytalk),然后把它称为底部(“ _|_
”,写成像一个反转的T)。 它可能代表一个exception或无限循环或死锁或“只是等待更长时间”。 (有些函数只能返回底部,如果其中一个参数是底部。)
当您创build这些types的笛卡尔积/元组时,您将观察到相同的行为: (Bool,Bool,Bool,(),())
具有2·2·2·1·1 = 6个不同的值。 (Bool,Bool,Bool,(),Void)
类似于具有2·2·2·1·的集合{t,f}×{t,f}×{t,f}×{u} 0 = 0个元素,除非您将_|_
计为一个值。
又一个angular度:
()
是包含名为()
的单个元素的集合的名称。
它确实有点混乱,在这种情况下,集合的名称和元素恰好相同。
记住:在Haskell中,一个types是一个具有其可能值作为元素的集合。