为啥 Haskell 没有符号(a la ruby)/原子(a la erlang)?
Posted
技术标签:
【中文标题】为啥 Haskell 没有符号(a la ruby)/原子(a la erlang)?【英文标题】:Why doesn't Haskell have symbols (a la ruby) / atoms (a la erlang)?为什么 Haskell 没有符号(a la ruby)/原子(a la erlang)? 【发布时间】:2011-09-04 02:08:54 【问题描述】:我使用符号的两种语言是 Ruby 和 Erlang,我一直发现它们非常有用。
Haskell 确实有代数数据类型,但我仍然认为符号会非常方便。立即想到的一个用途是,由于符号与整数同构,因此您可以在使用整数或字符串“主键”的地方使用它们。
原子的语法糖可以是次要的 - :something 或
type ErrorCode = Atom
type Message = String
data Error = Error ErrorCode Message
loginError = Error :redirect "Please login first"
在这种情况下 :redirect 比使用字符串(“重定向”)更有效,并且比整数(404)更容易理解。
好处可能看起来微不足道,但我认为值得将原子添加为语言功能(或至少是 GHC 扩展)。
那么为什么没有在语言中添加符号呢?还是我想错了?
【问题讨论】:
不会出现错误代码之类的情况,您需要一组预定义的值,而不是允许任意可能是无意义的东西吗?大概在其他地方会有处理错误的代码,你想确保只给它知道如何处理的东西。 不一定。我可能想使用我想出的错误代码,而不必首先将整个错误集定义为数据类型。处理程序代码可以简单地处理它想要处理的情况,同时将其余部分集中在默认处理程序中。 这对 Haskell 来说似乎不是很地道。但即便如此,我认为提到的库 sclv 就足够了,所以我想我仍然不明白为什么它会有很大的不同。 【参考方案1】:我同意 camccann 的回答,它可能主要是因为它必须深入实施,而且对于这种复杂程度来说用处太少。在 Erlang(以及 Prolog 和 Lisp)中,符号(或原子)通常用作特殊标记,并且与构造函数的概念基本相同。在 Lisp 中,动态环境包括编译器,因此它在一定程度上也是一个(有用的)编译器概念泄漏到运行时。
问题如下,符号实习是不纯的(它修改了符号表)。因为我们从不修改现有对象,所以它是引用透明的,但是如果天真地实现可能会导致运行时空间泄漏。事实上,正如目前在 Erlang 中实现的那样,你实际上可以通过实习太多的符号/原子来使 VM 崩溃(我认为当前限制是 2^20),因为它们永远不会被垃圾收集。如果没有围绕符号表的巨大锁,在并发设置中也很难实现。
但是,这两个问题都可以(并且已经)解决。例如,请参阅Erlang EEP 20。我在simple-atom 包中使用了这种技术。它在后台使用unsafePerformIO
,但仅在(希望如此)极少数情况下使用。它仍然可以使用 GC 的一些帮助来执行类似于间接缩短的优化。它还在内部使用了相当多的IORef
s,这对于性能和内存使用来说并不是太好。
总而言之,它是可以做到的,但正确实施它并非易事。编译器编写者总是权衡功能的功能与其实现和维护工作,似乎一流的符号在这一点上失败了。
【讨论】:
+1 用于提供有关实施 atom 的成本的更多信息。我开始同意原子在 Haskell 中可能不是非常有用。【参考方案2】:Haskell 使用类型构造器*而不是符号,因此函数可以采用的符号集是封闭的,并且可以通过类型系统进行推理。您可以将符号添加到语言中,但它会将您置于使用字符串的相同位置 - 您必须在运行时检查所有可能的符号与少数具有已知含义的符号,在所有地方添加错误处理等。对于所有编译时检查来说,这将是一个很大的解决方法。
字符串和符号之间的主要区别是interning - 符号是原子的,可以在恒定时间内进行比较。不过,两者都是具有本质上无限数量的不同值的类型,并且与 Haskell 指定参数和具有有限类型的结果的粒度背道而驰。
我对 OCaml 比对 Haskell 更熟悉,因此“类型构造函数”可能不是正确的术语。None
或 Just 3
之类的东西。
【讨论】:
为了精确起见:“类型构造函数”构造类型,“数据构造函数”构造数据,即值。所以Nothing :: Maybe a
和Just :: a -> Maybe a
是数据构造函数,而Maybe :: * -> *
是类型构造函数。毫无疑问,你会因为这个重要的细节而感到更加开明。 ;)
字符串和原子的区别在于你可以在运行时构造新的字符串,但是原子不能以任何方式被操纵。它们应该只具有预定义的 Show 和 Eq 实例(可能还有 Ord)。这应该允许有效的编译策略。【参考方案3】:
立即想到的一个用途是,由于符号与整数同构,因此您可以在使用整数或字符串“主键”的地方使用它们。
请改用Enum
。
data FileType = GZipped | BZipped | Plain
deriving Enum
descr ft = ["compressed with gzip",
"compressed with bzip2",
"uncompressed"] !! fromEnum ft
【讨论】:
emums/adts 的问题是它们只有在编译时知道值时才起作用。当值在运行时确定时,原子/内部字符串对于编译器等情况特别方便。 @sclv:那么我一定误解了 OP,因为在编译时不可知的符号与(固定范围)整数没有任何非平凡的同构。 好吧,直到你有太多,然后这是一个错误:-) 嗯,我在想编译时已知的原子。但是每次你需要使用一个数据结构/枚举时定义一个数据结构/枚举有点痛苦。此外,您需要使 ADT 可用于使用它的每一位代码,而符号会被自动理解。 @Anupam 我认为这是一种很好的痛苦。相反,使用 ADT 会迫使您将“原子”组合成(希望是)有意义的类型。:plain :: Atom
可能意味着很多事情; Plain :: FileType
的含义更清晰,允许编译器仅在有意义的地方帮助您使用它。【参考方案4】:
我认为最简单的答案是,在使用 Lisp 样式的符号(我相信 Ruby 和 Erlang 都有这个想法)的用途中,在 Haskell 中大多数是:
已经以其他方式完成——例如具有一堆空构造函数的数据类型,它们也表现为“整数的方便名称”。
难以适应——存在于语言语法级别而不是常规数据的事物通常具有与其相关联的更多类型信息,但符号必须要么是彼此不同的类型(几乎没用没有某种轻量级的临时求和类型)或所有相同的类型(在这种情况下,它们与仅使用字符串几乎没有区别)。
另外,请记住,Haskell 本身实际上是一种非常非常小的语言。很少有“烘焙”的东西,其中大部分只是其他原语的语法糖。如果你包含了一堆 GHC 扩展,这有点不太正确,但是带有 -XAndTheKitchenSinkToo 的 GHC 与 Haskell 本身不是同一种语言。
此外,Haskell 非常适合伪语法和元编程,因此即使没有内置它,您也可以做很多事情。特别是如果您进入 TH 和可怕的类型元编程等等。
所以它主要归结为符号的大部分实际用途已经可以从其他功能中获得,而那些不可用的东西将比它的价值更难添加。
【讨论】:
我很确定 Erlang 在 Prolog 中得到了这个想法,它可能受到了 Lisp 的影响,但同样可能受到 1960 年代其他研究语言的影响。 @larsmans:哦,好点。我不知道为什么我忘记了 Prolog 对 Erlang 的影响。这确实更有意义。 Erlang 肯定是从 Prolog 得到的,Prolog 是从 Lisp 得到的。 添加原子似乎是对 Haskell 的一个非常易于使用和实施的更改。此外,我可以想象它会导致简洁而漂亮的haskell 代码,我认为这对许多haskellers 来说很重要。如果我们必须使用“可怕”的 TH/元编程,那么美感和简洁性就会丢失。 @Anupam Jain:您想到了什么样的特殊语法,您希望如何使用该功能?老实说,对我来说,好处似乎很小。【参考方案5】:语言不提供原子,但可以作为库合理地实现:
http://hackage.haskell.org/package/simple-atom
还有一些其他关于 hackage 的库,但这个看起来是最新的并且维护良好。
【讨论】:
谢谢,看起来很有趣!但正如我在上面的评论中所说,如果我们没有内置的原子语法糖,那么美感和简洁性就会失去,这是使用它们的主要动机。以上是关于为啥 Haskell 没有符号(a la ruby)/原子(a la erlang)?的主要内容,如果未能解决你的问题,请参考以下文章
是否有类似于 Haskell 的 $(美元符号)的 Scala 运算符?
为啥在 ruby 中动态创建大量符号不是一个好主意(对于 2.2 之前的版本)?
为啥 Haskell 没有在函数签名中推断数据类型的类型类?