()在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存储在节点的元素,其值由值构造函数LeafNode 。 我喜欢颜色types构造函数(树)蓝色和值构造函数(叶,节点)红色。 在expression式中不应该有蓝色,除非你使用高级特性,否则不能使用红色。 内置的Booltypes可以被声明,

 data Bool = True | False 

将蓝色Bool添加到types语言中,并将红色TrueFalse到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的地方使用,但实际上并不需要传达任何信息。 这有几个用途。

IOState这样的一元事物具有回报价值,也有副作用。 有时候,操作的唯一要点是执行副作用,如写入屏幕或存储某种状态。 为了写入屏幕, putStrLn必须有typesString -> IO ? IO总是有一些返回types,但这里没有什么有用的返回。 那么我们应该回报什么types? 我们可以说诠释,并总是返回0,但这是误导。 所以我们返回() ,只有一个值的types(因此没有有用的信息),表示没有什么有用的回来。

有一个types可能没有任何有用的价值是有用的。 考虑一下,如果你已经实现了一个Map kvtypes的Map kv映射ktypes的键值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..." 

它被称为Unittypes,通常用来表示副作用。 你可以隐约地把它想成Java中的Void 。 在这里和这里阅读更多。什么可以混淆是()语法表示types和它的唯一值字面值。 另外请注意,它不类似于Java中的null ,这意味着一个未定义的引用 – ()实际上是一个0大小的元组。

我真的很喜欢用元组来比喻()

(Int, Char)IntChar的所有对的types,所以它的值是与所有可能的Char相交的Int所有可能值。 (Int, Char, String)类似于(Int, Char, String)的所有三元组的types。

很容易看到如何继续向上延伸这种模式,但是向下呢?

(Int)将是“一元组”types,由Int的所有可能值组成。 但是Haskell会把这个解释为只是把Int括在括号内,因此就是Inttypes。 在这种types的值将是(1)(2)(3)等,这也只是被parsing为括号中的普通的Int值。 但是如果你仔细想想,一个“一元组”就和一个单一的值完全一样,所以没有必要实际存在它们。

向下进一步到零元组给我们() ,这应该是在空的types列表中的所有可能的值组合。 那么,只有一种方法可以做到这一点,即不包含其他值,所以type ()只应该有一个值。 通过与元组值的语法类比,我们可以将该值写为() ,它看起来像一个不包含值的元组。

这正是它的工作原理。 没有什么魔法,这种types()及其值()并不是由语言特别处理的。

()实际上在LYAH书籍的monad例子中并不被视为“任何types的空值”。 每当使用type ()唯一可以返回的值是() 。 所以它被用作一个types来明确地说, 不能有任何其他的返回值。 同样地,其他types应该返回,你不能返回()

需要注意的是,当一堆monadic计算与>>=>>等的块或运算符一起组合时,它们将为某些monad m构build一个types为ma的值。 mselect必须在整个组件部分保持不变(这样就没有办法用IO Int来组成一个Maybe Int ),但是在每个阶段, a和can经常是不同的。

所以当有人在IO String计算的中间插入一个IO ()时,并不是使用()作为Stringtypes的空值,而是简单地在构buildIO String的方式上使用 IO ()你可以使用一个Int来build立一个String

混淆来自其他编程语言:“无效”意味着在大多数命令式语言中,存储器中没有存储结构的结构。 它似乎不一致,因为“布尔”有2个值,而不是2位,而“无效”没有位,而不是没有值,但这是关于实际意义上的函数返回。 准确地说:它的单个值不消耗任何存储空间。

让我们暂时忽略价值底部(书面_|_ )…

()被称为Unit,写成一个空元组。 它只有一个价值。 而且它不被称为Void ,因为Void甚至没有任何值,因此不能被任何函数返回。


观察这个: Bool有2个值( TrueFalse ), ()有一个值( () ),而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是一个具有其可能值作为元素的集合。