究竟什么是Haskell中的()?

时间:2013-06-03 08:20:27

标签: haskell types unit-type

我正在阅读了解你一个Haskell ,在monad章节中,在我看来,()被视为每种类型的“null”。当我检查GHCi中()的类型时,我得到了

>> :t ()
() :: ()

这是一个非常混乱的陈述。似乎()本身就是一种类型。我很困惑它是如何融入语言的,以及它如何能够代表任何类型。

6 个答案:

答案 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,其值由值构造函数LeafNode给出。我喜欢将颜色类型构造函数(树)蓝色和值构造函数(叶,节点)红色。表达式中应该没有蓝色,并且(除非您使用高级功能)类型中没有红色。可以声明内置类型Bool

data Bool = True | False

将蓝色Bool添加到类型语言,将红色TrueFalse添加到表达式语言。可悲的是,我的降价功能不足以为这篇文章添加颜色,所以你只需要学会在头脑中添加颜色。

"单位" 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)

()类型可以被认为是零元素元组。它是一种只能有一个值的类型,因此它用于需要类型的地方,但实际上并不需要传达任何信息。这有几个用途。

IOState之类的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。阅读更多herehere等。令人困惑的是()语法上代表了类型及其唯一的值字面值。另请注意,它与Java中的null不相似,这意味着未定义的引用 - ()实际上只是一个0大小的元组。

答案 3 :(得分:7)

我真的很想通过类比元组来考虑()

(Int, Char)IntChar的所有对的类型,因此它的值是IntChar的所有可能值的所有可能值1}}。 (Int, Char, String)类似于IntCharString的所有三元组的类型。

很容易看出如何继续向上扩展这种模式,但是向下呢?

(Int)将是“1-tuple”类型,由Int的所有可能值组成。但是,Haskell会将其解析为仅在Int周围放置括号,因此只是类型Int。此类型中的值为(1)(2)(3)等,它们也会在括号中被解析为普通Int值。但是如果你考虑一下,“1元组”与单个值完全相同,所以没有必要让它们存在。

向下进一步向零元组提供(),这应该是空类型列表中所有可能的值组合。好吧,只有一种方法可以做到这一点,即不包含其他值,因此类型()中只应有一个值。通过类比元组值语法,我们可以将该值写为(),当然看起来就像一个不包含值的元组。

这正是它的工作原理。没有魔法,这种类型()及其值()绝不会被语言特别对待。

()实际上并未被视为LYAH书中monad示例中的“任何类型的空值”。每当使用类型()时,可以返回的值为()。所以它被用作显式的类型,表示不能任何其他返回值。同样,如果要返回另一种类型,则无法返回()

要记住的是,当一堆monadic计算与do块或>>=>>等运算符组合在一起时,它们将构建一个某些monad m a的{​​{1}}类型的值。 m的选择必须在整个组成部分保持不变(无法用mMaybe 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有两个值(TrueFalse),()有一个值(())和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中,类型是一个集合,其可能的值作为元素包含在其中。