我正在阅读Learn Has a Haskell,在monad章节中,对我来说,()
每种类型都将其视为一种“空值”。当我检查()
GHCi的类型时,我得到
>> :t ()
() :: ()
这是一个非常令人困惑的声明。看来这()
本身就是一种类型。我对它如何适合该语言以及它似乎能够代表任何类型感到困惑。
我正在阅读Learn Has a Haskell,在monad章节中,对我来说,()
每种类型都将其视为一种“空值”。当我检查()
GHCi的类型时,我得到
>> :t ()
() :: ()
这是一个非常令人困惑的声明。看来这()
本身就是一种类型。我对它如何适合该语言以及它似乎能够代表任何类型感到困惑。
Answers:
tl; dr ()
不会为每种类型添加“ null”值,地狱号;()
是其自身类型中的“暗淡”值:()
。
让我从问题中退后一步,解决常见的混乱根源。学习Haskell时要吸收的关键是它的表达语言和类型语言之间的区别。您可能知道这两者是分开的。但这允许在两者中使用相同的符号,这就是这里发生的情况。有简单的文字提示可以告诉您您正在使用哪种语言。您无需解析整个语言即可检测到这些提示。
默认情况下,Haskell模块的顶层使用表达式语言。您可以通过在表达式之间编写方程式来定义函数。但是,当您在表达式语言中看到foo :: bar时,表示foo是一个表达式,而bar是其类型。因此,当您阅读时() :: ()
,会看到一条语句,该语句()
将表达式语言中的与()
类型语言中的相关联。这两个()
符号含义不同,因为它们的语言不同。这种重复通常会使初学者感到困惑,直到表达/类型语言的分离安装在他们的潜意识中,这时它便成为了助记符。
关键字data
引入了一个新的数据类型声明,涉及表达式和类型语言的仔细混合,因为它首先说明了新类型是什么,其次是其值。
数据TyCon tyvar ... tyvar = ValCon1类型...类型| ... | ValConn类型...类型
在这样的声明中,类型构造函数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
到表达式语言。可悲的是,我的markdown-fu不足以为帖子添加颜色,因此您只需要学习在脑海中添加颜色即可。
“单位”类型()
用作特殊符号,但其工作方式类似于声明
data () = () -- the left () is blue; the right () is red
意思是,蓝色()
在类型语言中是类型构造函数,但是红色()
在表达式语言中实际上是值构造函数() :: ()
。[这不是这种双关语的唯一例子。大元组的类型遵循相同的模式:配对语法就像由
data (a, b) = (a, b)
在类型和表达式语言中都添加(,)。但是我离题了。
因此()
,通常称为“ Unit”的类型是一种包含一个值得说的值的类型:该值是用()
表达式语言编写的,有时称为“ void”。只有一个值的类型不是很有趣。类型值()
贡献零信息位:您已经知道它必须是什么。因此,尽管没有什么特别的类型()
可以指示副作用,但它通常以单调类型显示为值成分。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
,effect-monadState s
和值输出类型()
。当您将其()
视为值输出类型时,仅表示“此操作仅出于其效果;所传递的值没有意义”。相似地
putStr :: String -> IO ()
将字符串传递给stdout
但不返回任何令人兴奋的信息。
该()
类型还可用作容器状结构的元素类型,它表示数据仅由shape组成,没有有趣的有效负载。例如,如果Tree
如上所述声明,Tree ()
则为二叉树形状的类型,在节点上不存储任何感兴趣的内容。同样[()]
,沉闷元素列表的类型也是如此,如果列表元素中没有什么令人感兴趣的内容,那么它贡献的唯一信息就是它的长度。
概括起来,()
是一种类型。它的一个值()
恰好具有相同的名称,但这没关系,因为类型和表达式语言是分开的。具有表示“无信息”的类型很有用,因为在上下文中(例如,单子或容器),它告诉您只有上下文才是有趣的。
<pre>data <b>Bool</b> = <i>True</i> | <i>False</i></pre>
,但是鉴于SO降价的局限性,我担心这是最好的选择。另一种选择是使用图像。
()
“无效”吗?这似乎是错误的,因为“空缺”对我来说没有任何价值。
该()
类型可以认为是零元素元组。它是一个只能具有一个值的类型,因此可以在需要具有类型的地方使用,但实际上不需要传达任何信息。这有两个用途。
单子事物喜欢IO
并State
具有返回值,并且会产生副作用。有时,操作的唯一目的是执行副作用,例如写入屏幕或存储某些状态。要写入屏幕,putStrLn
必须具有类型String -> IO ?
-IO
始终必须具有一些返回类型,但是这里没有有用的返回值。那么我们应该返回什么类型呢?我们可以说Int,并且总是返回0,但这是一种误导。因此,我们返回()
,该类型只有一个值(因此没有有用的信息),以指示没有有用的返回。
有时可能会有一个没有有用值的类型,这很有用。考虑是否实现了将type的Map k v
键映射到typek
的值的type v
。然后,您想要实现一个Set
与映射非常相似的,只是不需要值部分,而只需要键。在Java之类的语言中,您可能会使用布尔值作为伪值类型,但实际上您只想要一个没有有用值的类型。所以你可以说type Set k = Map k ()
应当指出,这()
并不是特别神奇。如果需要,可以将其存储在变量中并对其进行模式匹配(尽管没有太多意义):
main = do
x <- putStrLn "Hello"
case x of
() -> putStrLn "The only value..."
我真的很想()
比喻为元组。
(Int, Char)
是anInt
和a的所有对的类型Char
,因此它的值是Int
与的所有可能值相交的所有可能值Char
。(Int, Char, String)
同样是an Int
,aChar
和a的所有三元组的类型String
。
很容易看出如何保持这种模式向上扩展,但是向下如何呢?
(Int)
将是“ 1-元组”类型,由的所有可能值组成Int
。但这可以被Haskell解析为只是Int
加上括号,从而成为类型Int
。而在这种类型的值是(1)
,(2)
,(3)
等,这也将只得到解析为普通的Int
括号内的数值。但是,如果考虑一下,“ 1-元组”与单个值完全相同,因此实际上不需要存在它们。
再往下走到零元组,我们得到()
,这应该是空类型列表中所有可能的值组合。嗯,确实有一种方法可以做到这一点,即不包含其他值,因此type中应该只有一个值()
。并且类似于元组值语法,我们可以将该值写为()
,这肯定看起来像一个不包含任何值的元组。
这就是它的工作原理。没有魔力,这种语言()
及其类型()
也不会被特殊对待。
()
在LYAH书中的monads示例中,实际上不被视为“任何类型的null值”。每当使用该类型时()
,唯一可以返回的值是()
。因此,它用作明确表示不能有其他返回值的类型。同样,在应该返回其他类型的地方,也不能返回()
。
要记住的事情是,当一元计算的一堆与组合在一起do
的块或类似的经营者>>=
,>>
等等,他们将建立一个类型的值,m a
对于一些单子m
。的这一选择m
已经留在整个零件(有没有办法组成一个同样Maybe Int
具有IO Int
以这种方式),但a
可以而且往往是在每个阶段不同。
因此,当有人坚持的IO ()
在中间IO String
计算,这是不使用()
作为一个空String
类型,它只是使用的IO ()
道路上的建设IO String
,以同样的方式,你可以使用一个Int
的方式来构建String
。
混乱来自其他编程语言:“无效”在大多数命令式语言中表示内存中没有存储值的结构。这似乎不一致,因为“布尔”有2个值,而不是2位,而“作废”没有位的,而不是没有价值,但它是关于什么在实际意义上的函数返回。确切地说:它的单个值不占用任何存储空间。
让我们_|_
暂时忽略值bottom(写成)...
()
称为单元,写为空元组。它只有一个值。而且它没有被调用
Void
,因为Void
甚至没有任何值,因此不能由任何函数返回。
请注意:Bool
具有2个值(True
和False
),()
具有一个值(()
),并且Void
没有值(不存在)。它们就像带有两个/一个/没有元素的集合。他们需要存储的最少内存是1位/无位/不可能。这意味着返回a的函数()
可能返回的结果值(显而易见的结果)可能对您没有用。Void
另一方面,这意味着该函数将永远不会返回,也不会给您任何结果,因为将不存在任何结果。
如果您想给“那个值”起一个函数返回的名称,而该函数永不返回(是的,这听起来像是疯话),然后将其命名为“底部”(“ _|_
”,就像倒T一样)。它可能表示异常,无限循环,死锁或“等待更长的时间”。(如果某些函数的参数之一是bottom,则某些函数只会返回bottom。)
创建笛卡尔乘积/这些类型的元组时,将观察到相同的行为:
(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个元素,除非您算作_|_
一个值。
Unit
类型。它只有一个值。。不返回任何有趣值的动作。