【问题标题】:Why no destructing in def form?为什么不破坏 def 形式?
【发布时间】:2012-01-02 02:19:46
【问题描述】:

let 表单中(这里是 Clojure)我可以做类似的事情

(let [[u s v] (svd A)] 
   (do-something-with u v))

svd 返回一个长度为 3 的列表。这是很自然的事情,为什么我们没有呢

(def [u s v] (svd A))

及其各种概括为def 表单的默认行为?我看不出这会如何干扰def 已经在做的任何事情。了解 Lisp 或 Clojure 之禅的人能否解释一下为什么 def 不支持像 let 那样强大的绑定(带解构)?

【问题讨论】:

    标签: clojure lisp let function destructuring


    【解决方案1】:

    def 是编译器级别的一种特殊形式:它生成一个 Var。 def 必须在解构可用之前可用且可用。你会看到与let* 类似的东西,这是一个不支持解构的编译器原语:然后在clojure/core.clj 中的几千行之后,该语言终于足够强大,可以提供一个带有解构的let 版本,作为@ 之上的宏987654327@。

    如果需要,您可以编写一个宏(例如,def+)来为您执行此操作。我个人认为这有点恶心,不会使用它,但使用 Lisp 意味着要使用适合您个人的语言。

    【讨论】:

    • 我认为这是我感兴趣的答案。冒着过度编辑的风险,我猜你是说在 Clojure 中没有这样做的部分原因是技术性的(因为def 恰好是一个编译器原语),并且部分是按照惯例(其中一个(例如 Rich Hickey)可以从原语 def* 开始,然后在内核中的某个位置声明 def)。跨度>
    • @GabrielMitchell 是的,这是可能的。但它对def 的用处远不如let,而且缺乏对称性。 let always 接受一个向量并在其中解构;让def 这样做会使其不那么方便,并且让 def 接受符号或解构形式是非常糟糕的 IMO。
    • 你能多说一下为什么这样的宏会很恶心吗?现在,我认为这是一个好主意——但是,根据您的评论,我也想知道它是否会以某种方式违背 Clojure 的原则。通常,最好顺应语言的本质,而不是安装一些与它相悖但似乎对没有语言经验的人有吸引力的东西。 10 年后,使用这样的宏并不常见,这一事实进一步让我怀疑它是否是解决问题的笨拙解决方案,最好以另一种方式解决。
    • 我认为原因之一是defs 通常是非常简单的对象,其属性在编译时是已知的。例如,函数、数字或关键字。对于更大、更复杂的对象,您通常不应该在编译时计算它们。宏也有点难以连贯地编写,因为(destructure [[x [y]] (foo)]) 包含临时向量以及xy 的生成对称名称。
    • 我写了一个def+宏。 stackoverflow.com/a/48998289/345427 我可以从 repl 中看到它的用处。正如@amalloy 所说,gensyms 是一个问题。有没有办法检测 let-var 是 gensym?
    【解决方案2】:

    这并不完美,但它是从写def+ 开始的 https://clojuredocs.org/clojure.core/destructure

    (defmacro def+
      "binding => binding-form
      internalizes binding-forms as if by def."
      {:added "1.9", :special-form true, :forms '[(def+ [bindings*])]}
      [& bindings]
      (let [bings (partition 2 (destructure bindings))]
        (sequence cat 
          ['(do) 
           (map (fn [[var value]] `(def ~var ~value)) bings)
           [(mapv (fn [[var _]] (str var)) bings)]])))
    

    你可以这样做......

    (def+ [u s v] [1 5 9], foo "bar")
    

    ...同时不影响def...的简单性...

    (def+ foo "bar")
    

    ...这是要求和建议的。 这仍然存在引入的问题 gensym 变量到全局命名空间中。 gensym 问题可以处理,但给出 用例(在repl中使用)附加变量 可能是可以接受的。

    【讨论】:

    • 我正在使用它来帮助我将 def 行从 let 绑定到我的 REPL 中,这样我就可以单独评估 let 的子形式。谢谢!
    【解决方案3】:

    def 基本上是 Vars 的构造函数。第一个参数是命名 Var 的符号。它接受该符号并为该符号返回一个 Var。解构会改变这些语义。

    不过,您可以编写一个宏来执行此操作。

    【讨论】:

    • 你所说的let 不一样吗?我的意思是不能用同样的论点说let 不应该支持解构绑定吗?
    • 是的,sepp2k 提出了我想知道的问题。据我了解,deflet 之间的基本区别在于绑定的范围,而let 似乎具有更一般的行为。正如 Chuck 指出的那样,您可以在第一个参数中编写一个多态的宏(单个符号的一种行为,符号列表的另一种行为)这一事实让我想知道为什么这不是默认实现。
    【解决方案4】:

    以下是一些哲学上的理由。

    Clojure 支持不变性而不是可变性,并且应仔细考虑和命名所有可变性来源。 def 创建可变变量。因此,惯用的 Clojure 无论如何都不会使用它们,并且也不希望在不小心(例如通过解构)的情况下创建许多可变变量太容易。但是,let 和函数参数解构会创建不可变绑定,因此 Clojure 使这些绑定易于创建。

    def 创建的变量具有全局范围。因此,您应该仔细命名defed 变量,并保持它们的数量很少。解构def 会使不小心创建许多def 变得太容易了。另一方面,let 和函数参数解构会创建局部的、词法范围的绑定,因此解构的便利性不会导致名称污染。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-04-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-20
      • 2015-03-25
      • 1970-01-01
      相关资源
      最近更新 更多