MYV*_*MYV 63 haskell types unit-type
我正在阅读" 了解你是一个哈斯克尔",在monad章节中,在我看来,()对于每种类型来说,它都被视为一种"空".当我检查()GHCi 的类型时,我得到了
>> :t ()
() :: ()
Run Code Online (Sandbox Code Playgroud)
这是一个非常令人困惑的声明.这似乎()是一种全部类型.我很困惑它是如何融入语言的,以及它如何能够代表任何类型.
pig*_*ker 133
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)
Run Code Online (Sandbox Code Playgroud)
声明Tree用于在节点处存储元素的二叉树类型的类型构造函数,其值由值构造函数Leaf和值给出Node.我喜欢将颜色类型构造函数(树)蓝色和值构造函数(叶,节点)红色.表达式中应该没有蓝色(除非您使用的是高级功能),类型中不应有红色.Bool可以声明内置类型,
data Bool = True | False
Run Code Online (Sandbox Code Playgroud)
加入蓝色Bool的类型语言,和红色True以及False到表达式语言.可悲的是,我的降价功能不足以为这篇文章添加颜色,所以你只需要学会在脑中添加颜色.
"unit"类型()用作特殊符号,但它的工作方式与声明的一样
data () = () -- the left () is blue; the right () is red
Run Code Online (Sandbox Code Playgroud)
意思是蓝色()是一种类型语言中的类型构造函数,但是一个名义上的红色()是表达式语言中的值构造函数,的确如此() :: ().[这不是这种双关语的唯一例子.较大元组的类型遵循相同的模式:对语法就像给出的一样
data (a, b) = (a, b)
Run Code Online (Sandbox Code Playgroud)
将(,)添加到类型和表达式语言中.但我离题了.
因此(),通常发音为"Unit"的类型是一个包含一个值的类型:该值是用()表达式语言编写的,有时发音为"void".只有一个值的类型不是很有趣.类型的值()贡献零位信息:您已经知道它必须是什么.因此,尽管类型没有任何特殊之处()来指示副作用,但它通常显示为monadic类型的值组件.Monadic操作往往具有类似的类型
val-in-type-1 -> ... -> val-in-type-n -> effect-monad val-out-type
返回类型是一个类型应用程序:该函数告诉您哪些效果是可能的,并且参数告诉您操作产生什么类型的值.例如
put :: s -> State s ()
Run Code Online (Sandbox Code Playgroud)
这是阅读(因为应用程序左侧["像我们在六十年代所做的那样",Roger Hindley])
put :: s -> (State s) ()
Run Code Online (Sandbox Code Playgroud)
有一个值输入类型s,效果monad State s和值输出类型().当您将其()视为值输出类型时,这仅表示"此操作仅用于其效果 ;传递的值无关紧要".同样
putStr :: String -> IO ()
Run Code Online (Sandbox Code Playgroud)
提供一个字符串stdout但不返回任何令人兴奋的东西.
该()类型也可用作容器类结构的元素类型,其中它表示数据仅包含形状,没有有趣的有效负载.例如,如果Tree声明如上,则Tree ()是二叉树形状的类型,在节点处不存储任何感兴趣的内容.类似的[()]是无聊元素列表的类型,如果列表的元素中没有任何兴趣,那么它贡献的唯一信息就是它的长度.
综上所述,()是一种类型.它的一个值()碰巧具有相同的名称,但这没关系,因为类型和表达式语言是分开的.有一个表示"无信息"的类型是有用的,因为在上下文(例如,monad或容器)中,它告诉你只有上下文是有趣的.
Nei*_*own 31
该()类型可以被认为是零元素元组.它是一种只能有一个值的类型,因此它用于需要类型的地方,但实际上并不需要传达任何信息.这有几个用途.
Monadic之类的东西,IO并且State具有返回值,以及执行副作用.有时,操作的唯一要点是执行副作用,例如写入屏幕或存储某些状态.对于写入屏幕,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..."
Run Code Online (Sandbox Code Playgroud)
我真的很想()通过与元组的类比来思考.
(Int, Char)是Inta和a 的所有对的类型Char,因此它的值是Int与所有可能值交叉的所有可能值Char.(Int, Char, String)类似地Int,a Char,a和a 的所有三元组的类型String.
很容易看出如何继续向上扩展这种模式,但是向下呢?
(Int)将是"1元组"类型,由所有可能的值组成Int.但是,这将被Haskell解析为只是将括号括起来Int,因此只是类型Int.而在这种类型的值是(1),(2),(3)等,这也将只得到解析为普通的Int括号内的数值.但是如果你考虑一下,"1元组"与单个值完全相同,所以没有必要让它们存在.
向下进一步向零元组提供给我们(),这应该是空类型列表中所有可能的值组合.好吧,有一种方法可以做到这一点,即不包含其他值,因此类型中只应有一个值().通过类比元组值语法,我们可以将该值写为(),这当然看起来像一个不包含值的元组.
这正是它的工作原理.没有魔法,这种类型()及其价值()决不会被语言特别对待.
()事实上,在LYAH书中的monads例子中,它并不被视为"任何类型的空值".每当使用类型时(),唯一可以返回的值是().因此它被用作一种类型来明确表示不能有任何其他返回值.同样,如果要返回另一种类型,则无法返回().
要记住的事情是,当一元计算的一堆与组合在一起do的块或类似的经营者>>=,>>等等,他们将建立一个类型的值,m a对于一些单子m.这种选择m必须在整个组成部分保持不变(没有办法Maybe Int用IO Int这种方式组成),但是a在每个阶段都可以并且经常是不同的.
因此,当有人坚持的IO ()在中间IO String计算,这是不使用()作为一个空String类型,它只是使用的IO ()道路上的建设IO String,以同样的方式,你可以使用一个Int的方式来构建String.
混淆来自其他编程语言:"void"意味着在大多数命令式语言中,存储器中没有存储值的结构.这似乎不一致,因为"布尔"有2个值,而不是2位,而"作废"没有位的,而不是没有价值,但它是关于什么在实际意义上的函数返回.确切地说:它的单个值不会消耗任何存储空间.
让我们_|_暂时忽略价值底部(写入)......
()被称为Unit,写成一个null-tuple.它只有一个值.它没有被调用
Void,因为Void甚至没有任何值,因此任何函数都无法返回.
观察这个:Bool有2个值(True和False),()有一个值(()),Void没有值(它不存在).它们就像有两个/一个/没有元素的集合.存储其值所需的最小内存分别为1位/无位/不可能.这意味着返回a的函数()可能会返回一个结果值(显而易见的),这可能对您没用.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个元素.
又一个角度:
()是包含一个名为的单个元素的集合的名称().
在这种情况下,集合的名称和元素恰好相同,这确实有点令人困惑.
请记住:在Haskell中,类型是一个集合,其可能的值作为元素.