为什么Clojure除了符号还有“关键字”?

141

我对其他Lisps(特别是Scheme)有一些了解。最近我一直在阅读Clojure方面的内容。我看到它既有“符号”又有“关键字”。我熟悉符号,但不熟悉关键字。

其他Lisps是否有关键字?除了使用不同的符号表示(即冒号),关键字与符号有何不同?


6个回答

150

这里是关于Keywords和Symbols的Clojure文档

Keywords是符号标识符,它们被求值为它们自己。它们提供非常快速的相等性测试...

Symbols是标识符,通常用于引用其他事物。它们可以在程序表单中用于引用函数参数、let绑定、类名和全局变量...

Keywords通常被用作轻量级的“常量字符串”,例如哈希映射的键或多方法的调度值。Symbols通常用于命名变量和函数,除了在宏等方面直接处理它们以外,不太常见将其用作对象进行操作。但如果您不介意一直加引号,完全可以在使用Keyword的地方都使用Symbol。

最简单的区别方法是阅读Clojure源代码中的Keyword.javaSymbol.java。有一些明显的实现差异。例如,在Clojure中,Symbol可以具有元数据,而Keyword则不行。

除了单冒号语法外,您还可以使用双冒号来制作命名空间限定的keyword。

user> :foo
:foo
user> ::foo
:user/foo

Common Lisp、Ruby和其他语言都有关键字。当然,它们在这些语言中略有不同。Common Lisp关键字和Clojure关键字之间的一些区别:

  1. Clojure中的关键字不是符号。

user> (symbol? :foo)  
false
  • 关键字不属于任何命名空间,除非你明确地对它们进行限定:

  • user> (namespace :foo)
    nil
    user> (namespace ::foo)
    "user"
    

    (感谢Rainer Joswig提供了我需要查看的内容。)


    11
    这解释了两者的区别,但并没有说明为什么需要这两个不同的构造。Clojure不能创造一个具有关键字和符号能力的结合体吗? - user73774
    27
    关键字轻量级且语法便捷,我认为这就是全部。没有它们语言也能正常工作,但有它们会更好,而且它们被广泛使用。你不能拥有它们的所有能力,因为关键字始终是自我评估的(即你不能将其用作变量或函数名),而一般情况下符号无法始终自我评估。 - Brian Carper
    2
    关键字似乎更适合作为哈希映射等中的键,因为它们一旦被评估就不会改变:(eval (eval ':a)) vs (eval (eval ''a))。还有其他优点吗?在性能方面,它们是相同的吗? - kristianlm
    6
    (identical? :qwe :qwe) -> true. (identical? 'qwe 'qwe) -> false. 符号(Symbols)在内部使用池化的字符串,因此比较速度也很快。 - desudesudesu

    31

    Common Lisp关键字符号。

    关键字也是符号。

    (symbolp ':foo) -> T
    

    特别之处在于:
    • :foo被Common Lisp读取器解析为符号keyword::foo
    • 关键字对其自身求值::foo -> :foo
    • 关键字符号的主包是KEYWORD包: keyword:foo -> :foo
    • 关键字从KEYWORD包导出
    • 关键字是常量,不允许分配不同的值

    否则,关键字是普通符号。因此,关键字可以命名函数或具有属性列表。

    请记住:在Common Lisp中,符号属于一个关键字包。这可以写成:

    • 当符号在当前包中可访问时,使用foo
    • 当符号FOO从包BAR导出时,使用foo:bar
    • 当符号FOO在包BAR中时,使用foo::bar

    对于关键字符号,这意味着:fookeyword:fookeyword::foo都是相同的符号。因此,后两种符号通常不使用。

    因此,:foo 被解析为位于 KEYWORD 包中,假设在符号名称之前没有给出包名称,这意味着默认情况下使用 KEYWORD 包。


    6
    关键字是符号,它们会自我评估,因此您不必记得将它们引用。

    6
    就这样了吗?使用冒号而不是分号似乎并没有太大的优势,特别是对于大多数键盘来说,冒号需要额外按下一个键。 - Laurence Gonsalves
    11
    其实不仅仅是字符的问题,关键字在评估后仍然是关键字,而符号则被评估为其所绑定的内容。这更像是一种语义上的差异,因为它们通常用于不同的目的。 - Greg Hewgill
    14
    Clojure中的关键字不是符号。 - David Plumpton

    5

    许多集合都会特别处理关键字,这使得一些非常方便的语法得以实现。

    (:user-id (get-users-map))
    

    是与

    相同的意思。
    ((get-users-map) :user-id)
    

    这可以使事情更加灵活。

    22
    符号也是如此,('a {'a 1 'b 2}) => 1 而 ({'a 1 'b 2} 'b) => 2。 - Jonas

    5

    对于关键词,当关键词首次构建时,会计算并缓存哈希值。在将关键词作为哈希键查找时,它只会返回预先计算的哈希值。对于字符串和符号,每次查找都会重新计算哈希。

    同名的关键词之所以始终相同,是因为它们包含自己的哈希值。由于在映射和集合中搜索是从哈希键进行的,因此在大量搜索的情况下可以提高搜索效率,而不是搜索本身。


    -1

    关键字是全局的,符号则不是。

    这个例子是用JavaScript编写的,但我希望它能够帮助理解这一点。

    const foo = Symbol.for(":foo") // this will create a keyword
    const foo2 = Symbol.for(":foo") // this will return the same keyword
    const foo3 = Symbol(":foo") // this will create a new symbol
    foo === foo2 // true
    foo2 === foo3 // false
    

    当你使用Symbol函数构建一个符号时,每次都会得到一个独特/私有的符号。当你通过Symbol.for函数请求一个符号时,每次都会得到相同的符号。
    (println :foo) ; Clojure
    

    System.out.println(RT.keyword(null, "foo")) // Java
    

    console.log(System.for(":foo")) // JavaScript
    

    这些都是一样的。


    函数参数名称是局部的,即不是关键字。

    (def foo (fn [x] (println x))) ; x is a symbol
    (def bar (fn [x] (println x))) ; not the same x (different symbol)
    

    为什么在这里使用JavaScript示例?这对我来说似乎很混乱。 - David J.
    因为我将许多Clojure代码移植到JavaScript中,两者很相似。你还可以在浏览器中无需任何麻烦地运行它。这是我解释这个概念最自然的方法。此外,它还为解释提供了另一个维度。因此,如果您恰好了解JavaScript,您可以利用这些知识将其映射到Clojure,随着学习的深入,您会越来越熟悉。JavaScript也是世界上最常见的编程语言之一。它不会伤害任何人。 - John Leidegren

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接