我正在阅读了解你一个Haskell ,在monad章节中,在我看来,()
被视为每种类型的“null”。当我检查GHCi中()
的类型时,我得到了
>> :t ()
() :: ()
这是一个非常混乱的陈述。似乎()
本身就是一种类型。我很困惑它是如何融入语言的,以及它如何能够代表任何类型。
答案 0 :(得分:129)
tl; dr ()
未添加" null"每种类型的价值,地狱没有; ()
是一个沉闷的"值的类型:()
。
让我从问题中退一步,解决混乱的常见根源。学习Haskell时要吸收的关键是它的表达式语言与其类型语言之间的区别。你可能已经意识到两者是分开的。但是这允许在两者中使用相同的符号,这就是这里发生的事情。有简单的文字提示可以告诉你你正在看哪种语言。您不需要解析整个语言来检测这些提示。
默认情况下,Haskell模块的顶层存在于表达式语言中。通过在表达式之间编写方程来定义函数。但是当你在表达式语言中看到 foo :: bar 时,这意味着 foo 是一个表达式而 bar 是它的类型。因此,当您阅读() :: ()
时,您会看到一种语句,该语句将表达式语言中的()
与类型语言中的()
相关联。这两个()
符号表示不同的东西,因为它们不是同一种语言。这种重复经常会引起初学者的困惑,直到表达/类型语言分离在他们的潜意识中安装,此时它变得有助于助记。
关键字data
引入了一个新的数据类型声明,涉及表达式和类型语言的仔细混合,因为它首先说明新类型是什么,其次是它的值。
data TyCon tyvar ... tyvar = ValCon1 type ... type | ... | ValConn type ... type
在这样的声明中,类型构造函数 TyCon 被添加到类型语言中, ValCon 值构造函数被添加到表达式语言(及其模式子语言) 。在data
声明中, ValCon 的参数位置中的内容告诉您在表达式中使用 ValCon 时为参数指定的类型。例如,
data Tree a = Leaf | Node (Tree a) a (Tree a)
为在节点处存储元素的二叉树类型声明类型构造函数Tree
,其值由值构造函数Leaf
和Node
给出。我喜欢将颜色类型构造函数(树)蓝色和值构造函数(叶,节点)红色。表达式中应该没有蓝色,并且(除非您使用高级功能)类型中没有红色。可以声明内置类型Bool
,
data Bool = True | False
将蓝色Bool
添加到类型语言,将红色True
和False
添加到表达式语言。可悲的是,我的降价功能不足以为这篇文章添加颜色,所以你只需要学会在头脑中添加颜色。
"单位" type使用()
作为特殊符号,但它的作用就像声明
data () = () -- the left () is blue; the right () is red
意味着一个名义上为蓝色的()
是类型语言中的类型构造函数,但是一个名义上的红色()
是表达式语言中的值构造函数,实际上是() :: ()
。 [这不是这种双关语的唯一例子。较大元组的类型遵循相同的模式:对语法就好像由
data (a, b) = (a, b)
将(,)添加到类型和表达式语言中。但我离题了。
因此类型()
,通常发音为" Unit",是一个包含一个值的类型:该值是()
但是在表达式语言中,并且是有时发音" void"。只有一个值的类型不是很有趣。类型()
的值提供零位信息:您已经知道它必须是什么。因此,尽管类型()
没有任何特殊之处可以指示副作用,但它通常显示为monadic类型中的值组件。 Monadic操作往往具有类似
val-in-type-1 -> ... -> val-in-type-n -> effect-monad val-out-type
其中返回类型是一个类型应用程序:该函数告诉您哪些效果是可能的,并且参数告诉您操作产生了什么类型的值。例如
put :: s -> State s ()
被读取(因为应用程序关联到左边["正如我们在六十年代所做的那样",Roger Hindley])
put :: s -> (State s) ()
有一个值输入类型s
,效果monad State s
和值输出类型()
。当您将()
视为值输出类型时,这仅表示"此操作仅用于其效果;交付的价值是无趣的#34;。类似地
putStr :: String -> IO ()
将字符串传递给stdout
,但不会返回任何令人兴奋的内容。
()
类型也可用作容器类结构的元素类型,它表示数据仅包含形状,没有有趣的有效负载。例如,如果Tree
如上所述,则Tree ()
是二叉树形状的类型,在节点处不存储任何感兴趣的内容。类似地,[()]
是无效元素列表的类型,如果列表的元素中没有任何兴趣,那么它所贡献的唯一信息就是它的长度。
总而言之,()
是一种类型。它的一个值()
碰巧具有相同的名称,但这很好,因为类型和表达式语言是分开的。有一种类型代表"没有信息"因为,在上下文中(例如,monad或容器),它告诉你只有上下文才有意思。
答案 1 :(得分:31)
()
类型可以被认为是零元素元组。它是一种只能有一个值的类型,因此它用于需要类型的地方,但实际上并不需要传达任何信息。这有几个用途。
IO
和State
之类的Monadic事件具有返回值,以及执行副作用。有时,操作的唯一要点是执行副作用,例如写入屏幕或存储某些状态。要写入屏幕,putStrLn
必须有String -> IO ?
类型 - IO
总是必须有一些返回类型,但这里没有什么用处可以返回。那么我们应该返回什么类型?我们可以说Int,并且总是返回0,但那是误导。所以我们返回()
,这个类型只有一个值(因此没有有用的信息),表明没有什么有用的回来。
有一种没有用价值的类型有时很有用。考虑一下您是否实现了一种类型Map k v
,它将k
类型的键映射到类型v
的值。然后你想实现一个Set
,它与地图非常相似,只是你不需要值部分,只需要键。在像Java这样的语言中,您可以使用布尔值作为虚拟值类型,但实际上您只需要一个没有有用值的类型。所以你可以说type Set k = Map k ()
应该注意()
并不是特别神奇。如果你想要你可以将它存储在一个变量中并在其上进行模式匹配(虽然没有多大意义):
main = do
x <- putStrLn "Hello"
case x of
() -> putStrLn "The only value..."
答案 2 :(得分:12)
它被称为Unit
类型,通常用于表示副作用。您可以在Java中模糊地将其视为Void
。阅读更多here和here等。令人困惑的是()
语法上代表了类型及其唯一的值字面值。另请注意,它与Java中的null
不相似,这意味着未定义的引用 - ()
实际上只是一个0大小的元组。
答案 3 :(得分:7)
我真的很想通过类比元组来考虑()
。
(Int, Char)
是Int
和Char
的所有对的类型,因此它的值是Int
与Char
的所有可能值的所有可能值1}}。 (Int, Char, String)
类似于Int
,Char
和String
的所有三元组的类型。
很容易看出如何继续向上扩展这种模式,但是向下呢?
(Int)
将是“1-tuple”类型,由Int
的所有可能值组成。但是,Haskell会将其解析为仅在Int
周围放置括号,因此只是类型Int
。此类型中的值为(1)
,(2)
,(3)
等,它们也会在括号中被解析为普通Int
值。但是如果你考虑一下,“1元组”与单个值完全相同,所以没有必要让它们存在。
向下进一步向零元组提供()
,这应该是空类型列表中所有可能的值组合。好吧,只有一种方法可以做到这一点,即不包含其他值,因此类型()
中只应有一个值。通过类比元组值语法,我们可以将该值写为()
,当然看起来就像一个不包含值的元组。
这正是它的工作原理。没有魔法,这种类型()
及其值()
绝不会被语言特别对待。
()
实际上并未被视为LYAH书中monad示例中的“任何类型的空值”。每当使用类型()
时,可以返回的仅值为()
。所以它被用作显式的类型,表示不能任何其他返回值。同样,如果要返回另一种类型,则无法返回()
。
要记住的是,当一堆monadic计算与do
块或>>=
,>>
等运算符组合在一起时,它们将构建一个某些monad m a
的{{1}}类型的值。 m
的选择必须在整个组成部分保持不变(无法用m
以Maybe Int
组合IO Int
,但a
可以和每个阶段往往都有所不同。
因此,当有人在IO ()
计算过程中粘贴IO String
时,()
类型中没有使用String
作为空值,它只是在构建IO ()
的过程中使用 IO String
,就像使用 Int
一样构建{ {1}}。
答案 4 :(得分:6)
混淆来自其他编程语言: “void”在大多数命令式语言中表示存储器中没有存储值的结构。它似乎不一致,因为“boolean”有2个值而不是2位,而“void”没有位而不是没有值,但它实际上是关于函数返回的 what 。确切地说:它的单个值不会消耗任何存储空间。
让我们暂时忽略价值底部(写成_|_
)......
()
被称为Unit,写成一个null-tuple。它只有一个值。并没有被称为
Void
,因为Void
甚至没有任何值,因此任何函数都无法返回。
观察:Bool
有两个值(True
和False
),()
有一个值(()
)和Void
没有价值(它不存在)。它们就像有两个/一个/没有元素的集合。存储其值所需的最小内存分别为1位/无位/不可能。这意味着返回()
的函数可能会返回一个对您无用的结果值(显而易见的值)。另一方面,Void
意味着该函数永远不会返回并且永远不会给你任何结果,因为不存在任何结果。
如果你想给“那个值”一个名字,那个函数会返回一个永不返回的名字(是的,这听起来像是疯狂的),然后把它称为底部(“_|_
”,写成反转的T) 。它可能代表异常或无限循环或死锁或“只是等待更长时间”。 (有些函数只会返回底部,如果它们的一个参数是底部的话。)
当您创建笛卡尔积/这些类型的元组时,您将观察到相同的行为:
(Bool,Bool,Bool,(),())
有2·2·2·1·1 = 6个不同的值。 (Bool,Bool,Bool,(),Void)
就像集{t,f}×{t,f}×{t,f}×{u}×{},其中有2·2·2·1·0 = 0个元素,除非你将_|_
计为值。
答案 5 :(得分:5)
又一个角度:
()
是包含名为()
的单个元素的集合的名称。
它确实有点令人困惑的是集合的名称和 在这种情况下,它中的元素恰好相同。
请记住:在Haskell中,类型是一个集合,其可能的值作为元素包含在其中。