Clojure没有let行吗?

Clojure没有let行吗?,clojure,Clojure,我发现我很少在Clojure中使用let。出于某种原因,我开始学习时不喜欢它,从那以后就一直避免使用它。当let出现时,感觉流停止了。我想知道,你认为我们可以完全不使用它吗?让我们提供一些好处。首先,它允许在函数上下文中进行值绑定。其次,它具有可读性优势。因此,虽然从技术上讲,人们可以取消它(在没有它的情况下,您仍然可以进行编程),但如果没有一个有价值的工具,该语言将变得贫瘠 let的一个优点是,它有助于形式化一种指定计算的通用(数学)方法,在这种方法中,您可以引入方便的绑定,然后作为结果引入一

我发现我很少在Clojure中使用
let
。出于某种原因,我开始学习时不喜欢它,从那以后就一直避免使用它。当
let
出现时,感觉流停止了。我想知道,你认为我们可以完全不使用它吗?

让我们提供一些好处。首先,它允许在函数上下文中进行值绑定。其次,它具有可读性优势。因此,虽然从技术上讲,人们可以取消它(在没有它的情况下,您仍然可以进行编程),但如果没有一个有价值的工具,该语言将变得贫瘠

let的一个优点是,它有助于形式化一种指定计算的通用(数学)方法,在这种方法中,您可以引入方便的绑定,然后作为结果引入一个简化的公式。很明显,绑定只适用于那个“范围”,它与一个更数学的公式相结合是有用的,特别是对于更多的函数式程序员


让块出现在其他语言(如Haskell)中并不是巧合。

您可以用
((fn[a1a2…])b1b2…)替换
(let[a1 b1 a2 b2…)
的任何出现。虽然我经常使用let,但我不想没有它

在防止宏中的多次执行时,Let对我来说是必不可少的:

(defmacro print-and-run [s-exp]
   `(do (println "running " (quote ~s-exp) "produced " ~s-exp)
        s-exp))
将运行两次s-exp,这不是我们想要的:

(defmacro print-and-run [s-exp]
  `(let [result# s-exp]
    (do (println "running " (quote ~s-exp) "produced " result#)
        result#))
通过将表达式的结果绑定到名称并两次引用该结果来修复此问题


由于宏返回的表达式将成为另一个表达式的一部分(宏是生成s表达式的函数),因此它们需要生成本地绑定以防止多次执行并避免符号捕获。

我想我理解您的问题。如果是错的,请纠正我。有时“let”用于命令式编程风格。比如说,

... (let [x (...)
          y (...x...)
          z (...x...y...)
          ....x...y...z...] ...
(... (let [a (...)
           b (...)...]
        (...a...b...a...b...) ;; still fp style
此模式来自命令式语言:

... { x = ...;
      y = ...x...;
      ...x...y...;} ...
你避免这种风格,这就是为什么你也避免“让”,不是吗

在某些问题中,命令式样式减少了代码量。此外,有时用java或c编写更高效。 此外,在某些情况下,“let”只保存子表达式的值,而不考虑计算顺序。比如说,

... (let [x (...)
          y (...x...)
          z (...x...y...)
          ....x...y...z...] ...
(... (let [a (...)
           b (...)...]
        (...a...b...a...b...) ;; still fp style

let
-绑定至少有两个重要用例:

首先,正确使用
let
可以使代码更清晰、更短。如果您有一个多次使用的表达式,那么将其绑定到
let
中非常好。下面是标准函数
map
的一部分,它使用
let

...
(let [s1 (seq c1) s2 (seq c2)]
  (when (and s1 s2)
    (cons (f (first s1) (first s2))
          (map f (rest s1) (rest s2)))))))
...
即使只使用一次表达式,给它起一个语义上有意义的名称(对未来的代码读者)仍然是有帮助的

其次,正如Arthur提到的,如果您想多次使用表达式的值,但只想对其求值一次,那么您不能简单地将整个表达式键入两次:您需要某种绑定。如果你有一个纯粹的表达,这只会是浪费:

user=> (* (+ 3 2) (+ 3 2))
25
但如果表达式有副作用,实际上会改变程序的含义:

user=> (* (+ 3 (do (println "hi") 2)) 
          (+ 3 (do (println "hi") 2)))
hi
hi
25

user=> (let [x (+ 3 (do (println "hi") 2))] 
         (* x x))                      
hi
25

最近偶然发现了这一点,因此进行了一些计时:

(testing "Repeat vs Let vs Fn"
  (let [start (System/currentTimeMillis)]
    (dotimes [x 1000000]
      (* (+ 3 2) (+ 3 2)))
    (prn (- (System/currentTimeMillis) start)))

  (let [start (System/currentTimeMillis)
        n (+ 3 2)]
    (dotimes [x 1000000]
      (* n n))
    (prn (- (System/currentTimeMillis) start)))

  (let [start (System/currentTimeMillis)]
    (dotimes [x 1000000]
      ((fn [x] (* x x)) (+ 3 2)))

    (prn (- (System/currentTimeMillis) start)))))

Output
Testing Repeat vs Let vs Fn
116
18
60

“let”胜过“pure”函数。

事实上,
(let[a1 b1 a2 b2]…)
可以用
((fn[a1](fn[a2]…)b1)b2)更好地解释。
。我的意思是,您可以在后续绑定中引用以前的绑定。编辑:括号,我们都喜欢。@Jan我想,但这意味着我不得不写更多的括号。我们现在不能这样,对吗?这是我经常使用的一种模式&我喜欢它事实上,我认为这个答案解决了问题:也许每次使用let都是一个考虑其他功能的机会?也许这表明代码的这一部分太复杂了?@Hendekagon当然你可以重构代码中的每一个漏洞。不过我倾向于在非常本地化的情况下使用它,我宁愿把一个函数弄得乱七八糟,也不愿把程序的其余部分弄得乱七八糟,因为我只需要在一个地方使用一行函数。我想你已经说到点子上了谢谢。如果你能把答案标为正确就好了;)我觉得你是在说像
(让[a1b1a2b2](someexpressiona1a2))
这样的东西有点必要。我不同意这一点。由于let在某种程度上等同于((fn[a1]…)b1)的
(let[a1 b1]…)
(参见立方体答案),因此应该清楚let是一个函数概念。您似乎调用了“命令式”编程的“串行求值”定义,但让串行调用与嵌套函数调用一样。当然,我看到了一点,即不熟悉FP的人倾向于使用let-heavy样式。对于这种情况,我倾向于在我的函数中添加多个可选参数列表:(defn f([x](fx(dosomthing x)))([xy]…),我不知道宏是否可以这样做,因为我从来没有写过宏!也许你会发现有趣的内容是使用函数而不是
来解释单子。不幸的是,不能相信这些计时-在Clojure中配置文件需要使用