对 Clojure 中的“let”感到困惑

Posted

技术标签:

【中文标题】对 Clojure 中的“let”感到困惑【英文标题】:Confused by "let" in Clojure 【发布时间】:2011-01-24 14:28:36 【问题描述】:

我刚开始玩 Clojure,我写了一个小脚本来帮助我理解一些功能。开头是这样的:

(def *exprs-to-test* [  
    "(filter #(< % 3) '(1 2 3 4 3 2 1))"
    "(remove #(< % 3) '(1 2 3 4 3 2 1))"
    "(distinct '(1 2 3 4 3 2 1))"
])

然后它通过*exprs-to-test*,评估它们,并像这样打印输出:

(doseq [exstr *exprs-to-test*]
    (do 
        (println "===" (first (read-string exstr)) "=========================")
        (println "Code: " exstr)
        (println "Eval: " (eval (read-string exstr)))
    )
)

以上代码一切正常。但是,(read-string exstr) 是重复的,所以我尝试使用 let 来消除重复,如下所示:

(doseq [exstr *exprs-to-test*]
    (let [ex (read-string exstr)] (
        (do 
            (println "===" (first ex) "=========================")
            (println "Code: " exstr)
            (println "Eval: " (eval ex))
        )
    ))
)

但这对*exprs-to-test* 中的第一个项目有效,然后以NullPointerException 崩溃。为什么添加let 会导致崩溃?

【问题讨论】:

【参考方案1】:

do 表单周围有一组额外的括号。您的代码正在这样做:

((do ...))

它试图执行(作为函数调用)整个do 表单的值,但do 返回nil,因为do 表单中的最后一个println 返回nil

请注意,您的缩进样式是非标准的。您不应该将结束括号放在自己的行上。而let 有一个隐含的do,所以你不需要在那里。试试这个:

user> (doseq [exstr *exprs-to-test*]
        (let [ex (read-string exstr)] 
          (println "===" (first ex) "=========================")
          (println "Code: " exstr)
          (println "Eval: " (eval ex))))
=== filter =========================
Code:  (filter #(< % 3) '(1 2 3 4 3 2 1))
Eval:  (1 2 2 1)
=== remove =========================
Code:  (remove #(< % 3) '(1 2 3 4 3 2 1))
Eval:  (3 4 3)
=== distinct =========================
Code:  (distinct '(1 2 3 4 3 2 1))
Eval:  (1 2 3 4)

【讨论】:

修复了它。也感谢缩进样式提示。【参考方案2】:

Brian 已经回答了你的问题,所以我只想为你提供一些关于 let-form 的一般指示:

Special forms(请参阅 let 部分) Bindings

【讨论】:

【参考方案3】:

我认为其他答案忽略了房间里的大象:你为什么要这样做?你的代码中有很多东西让我担心你通过学习 Clojure 走错了路:

使用全局绑定 (exprs-to-test) 使用doseq/println依次尝试代码 使用评估

学习 Clojure API 的最佳方式是通过 REPL。您应该设置好您的环境,无论是 Vim、Emacs 还是 IDE,以便您可以轻松地在文本文件中的静态代码和交互式 REPL 之间来回移动。 Here is a good breakdown of a number of Clojure IDEs.

现在,就您的代码而言,有几点需要记住。首先,几乎没有使用 eval 的充分理由。如果你发现自己这样做了,问问自己是否真的有必要。其次,请记住,Clojure 是一种函数式语言,通常您不需要使用“do”宏集。当您需要产生副作用时,“do”宏很有用(在您的示例中,副作用是 println 到 *out*) 最后,还应避免使用全局变量。如果确实需要使用 var,则应考虑使用绑定宏将线程本地的 var 绑定到不可变值,这样就不会出现并发问题。

我绝对建议您花时间学习 Clojure 编程或其他更深入的 LISP 参考资料,以真正了解您对编程方式的必要转变,以有效利用 Clojure。您在这里的小示例让我感觉好像您正在尝试在 Clojure 中编写命令式代码,这根本不会很好地工作。

【讨论】:

我知道全局变量在这 10 行脚本中只是失控了,我不会再使用“do”宏和 eval,即使在确实需要它们的情况下也是如此。我也会接受你关于 REPL 的建议,因为我对只使用了 10 分钟的语言有着完美的记忆,而且我不需要保存我的工作以供以后参考。作为一名经验丰富的 Clojure 专业人士,您正确的不回答让我感到羞耻。下次我会更加努力的。 多么令人作呕的讽刺和讽刺评论。我试图提供帮助。真丢脸。 我认为忽略这个问题并喋喋不休地谈论初学者的代码如何不完美是不合适的。感觉更像是在吹自己的喇叭,而不是试图提供帮助。 我想我明白了。您认为某人根据问题的感知基础发布更高级别的建议是不合适的。但是发布一个讽刺性的回复来侮辱那些花了一些时间写出合理的回复试图帮助你的人是完全合适的。你觉得人们质疑你的问题是一种侮辱,即使你可能走错了路。显然,您甚至看不到其他学习 Clojure 的人可能会在这里徘徊的价值看到您的“不完美”代码并认为这是一般做事的适当方式。 郑重声明,上周末我开始在 Clojure 中编写代码,所以我不是你脑海中对某些知识渊博的程序员的刻板印象。我是一个初学者,试图帮助另一个初学者(在从我最初的错误中吸取教训之后)。很抱歉伤害了你的自我。

以上是关于对 Clojure 中的“let”感到困惑的主要内容,如果未能解决你的问题,请参考以下文章

对 let 到 lambda 转换感到困惑

Clojure 发布年度调查报告:用于开发企业软件的比例历史最高

Clojure - 宏中的 let 不起作用

let 绑定中的 Clojure 函数

Clojure 交换!在 let 绑定中的 map 函数内不起作用

let 内的 Clojure 循环(全局 v 局部变量)