【问题标题】:Why does Clojure have "keywords" in addition to "symbols"?为什么 Clojure 除了“符号”之外还有“关键字”?
【发布时间】:2010-12-04 09:28:23
【问题描述】:

我从很久以前就对其他 Lisp(尤其是 Scheme)有了初步的了解。最近我一直在阅读有关Clojure 的信息。我看到它既有“符号”又有“关键字”。我熟悉的符号,但不是关键字。

其他 Lisp 有关键字吗?除了符号不同(即:冒号)之外,关键字与符号有何不同?

【问题讨论】:

标签: lisp clojure scheme keyword


【解决方案1】:

这是关键字和符号的Clojure documentation

关键字是对自身求值的符号标识符。它们提供了非常快速的相等性测试...

符号是通常用来指代其他事物的标识符。它们可以在程序形式中用于引用函数参数、let 绑定、类名和全局变量...

关键字通常用作轻量级的“常量字符串”,例如用于哈希映射的键或多方法的调度值。符号通常用于命名变量和函数,除了在宏等中之外,直接将它们作为对象操作不太常见。但是没有什么能阻止您在使用关键字的任何地方都使用符号(如果您不介意一直引用它们)。

查看差异的最简单方法是阅读 Clojure 源代码中的 Keyword.javaSymbol.java。有一些明显的实现差异。例如,Clojure 中的符号可以有元数据,而关键字则不能。

除了单冒号语法,你还可以使用双冒号来做一个命名空间限定的关键字。

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

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

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

    user> (symbol? :foo)  
    false
    
  2. 关键字不属于任何命名空间,除非您特别限定它们:

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

(感谢Rainer Joswig 给了我一些可以看的东西的想法。)

【讨论】:

  • 这解释了什么的区别,但没有解释为什么需要两种不同的结构。 Clojure 不能结合关键字和符号的功能创建一些东西吗?
  • 关键字是轻量级的并且具有方便的语法,我认为这就是它的全部内容。没有它们,该语言会很好地工作,但是拥有它们很好,并且它们被广泛使用。您不能将它们的能力结合起来,因为关键字始终是自我评估的(即您不能将它们用作变量或函数名称),而符号通常不能始终自我评估。
  • 似乎关键字作为哈希映射等中的键更有用,因为它们一旦评估就不会改变:(eval (eval ':a)) vs (eval (eval ''a))。还有其他优点吗?在性能方面,它们是相同的吗?
  • (相同?:qwe:qwe)-> 是的。 (相同?'qwe'qwe)-> 错误。符号使用内部字符串,所以比较也很快。
【解决方案2】:

Common Lisp 有关键字符号。

关键字也是符号。

(symbolp ':foo) -> T

是什么让关键字特别:

  • :foo 被 Common Lisp 阅读器解析为符号关键字::foo
  • 关键字自行评估::foo -> :foo
  • 关键字符号的 home 包是 KEYWORD 包:keyword:foo -> :foo
  • 关键字是从KEYWORD包中导出的
  • 关键字是常量,不允许赋值不同的值

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

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

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

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

所以 :foo 只是被解析为在包 KEYWORD 中,假设在符号名称之前没有给出包名称意味着默认情况下是 KEYWORD 包。

【讨论】:

    【解决方案3】:

    关键字是对自身求值的符号,因此您不必记住引用它们。

    【讨论】:

    • 是这样吗?键入 : 而不是 ' 似乎不是一个很大的胜利,尤其是因为 : 在大多数键盘上都是额外的按键。
    • 嗯,这不仅仅是角色,真的。关键字在评估后仍然是关键字,而符号被评估为它们绑定的任何内容。这更像是语义上的差异,因为它们通常用于不同的目的。
    • 关键字不是 Clojure 中的符号
    【解决方案4】:

    :关键字也被许多集合特殊处理,允许一些非常方便的语法。

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

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

    这让事情变得更加灵活

    【讨论】:

    • 符号也是如此,('a {'a 1 'b 2}) => 1 和 ({'a 1 'b 2} 'b) => 2。
    【解决方案5】:

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

    为什么相同命名的关键字总是相同的,它们包含自己的哈希值。 由于在地图和集合中的搜索是由哈希键组成的,因此在大量搜索的情况下,而不是在搜索本身中,这会带来更好的搜索效率。

    【讨论】:

      【解决方案6】:

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

      此示例是用 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 示例?这让我感到困惑。
      • @DavidJ。因为我将很多 Clo​​jure 代码移植到 JavaScript 并且映射得很好。您也可以毫不费力地在浏览器中运行它。这是我解释这个概念的最自然的方式。此外,它为解释增加了另一个维度。因此,如果您碰巧了解 JavaScript,您可以使用这些知识将其映射到 Clojure,因为您学习得越来越多。 JavaScript 也是世界上最常见的编程语言。它不会伤害任何人。
      猜你喜欢
      • 2012-07-24
      • 2017-04-20
      • 2020-12-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-31
      相关资源
      最近更新 更多