【问题标题】:Clojure functions - returning value computed before the last statementClojure 函数 - 在最后一条语句之前计算的返回值
【发布时间】:2013-06-18 14:37:39
【问题描述】:

我有一些用 clojure 编写的测试。这是一个简单的例子:

(defn test1
  []
  (start-server)
  (run-pvt-and-expect "PVT-0")
  (stop-server)
 )

我想返回“run-pvt-and-expect”的结果,但是我需要在它之后执行其他函数。如何在 clojure 中以功能方式执行此操作(不使用“let”之类的构造)?谢谢。

注意:我阅读了this 问题及其答案,但无法将其应用于我的案例。此外,有评论要求提供从未给出的示例,因此请将此视为示例...

【问题讨论】:

  • 您能解释一下您所说的“功能性”是什么意思吗?对我来说,let 没有任何非功能性,但您示例中的所有其他语句 都是非功能性的。

标签: function clojure functional-programming


【解决方案1】:

您可以使用 finally 块来做到这一点。

(try 
    (+ 2 3)
    (finally (+ 6 7)))

产生的结果是 5 而不是 13。

Here is the relevant doc page. 相关部分是:

将评估任何 finally 表达式的副作用。

另一种选择是编写一个类似 with-open 的宏来启动和停止服务器,同时返回正确的结果。虽然这只是将问题转移到如何编写宏上,因为它要么需要使用 let 要么需要使用上面的 try finally 构造。

【讨论】:

  • try/finally 是示例 as given 的唯一正确解决方案:我敢打赌,如果 run-pvt... 抛出,您仍然想停止服务器。
  • 谢谢。尽管我也喜欢 Omri Bernstein 建议的方法,但我最终还是使用了这种方法。谢谢。
【解决方案2】:

好吧,我不知道您可以使用任何内置的东西(除了let,您不想要它)。但美妙之处在于您可以自己构建它。

一个想法是创建一个“新”defn,其中表达式左侧的 :return 关键字表示从该表达式返回值,而不是最后一个表达式。此处适合使用宏,因为我们将构建修改后的 (defn ...) 表达式,这需要未计算的代码。

(defmacro defn-return-middle
  [nm arg-vec & body-exprs]
  `(defn ~nm ~arg-vec
     ~(if (some #{:return} body-exprs)
        (let [[before _ [to-be-returned & after]]
              (partition-by #{:return} body-exprs)]
          `(let [ret# (do ~@before ~to-be-returned)]
             ~@after
             ret#))
        body-exprs)))

这扩展为:

(defn-return-middle f []
  a
  :return b
  c)

类似于:

(defn f []
  (let [ret (do a b)]
    c
    ret))

例如,您现在可以这样做:

(defn-return-middle blah [a]
  (+ 1 a)
  :return (+ 2 a)
  (println "does this work?"))

现在在 REPL 中:

user> (blah 5)
does this work?
=>7

(耶!)

或者对于您的示例,您现在可以这样做:

(defn-return-middle test1
  []
  (start-server)
  :return (run-pvt-and-expect "PVT-0")
  (stop-server))

没错,宏使用let,但它通过自动扩展let 来工作,如果你每次都手写它。这意味着现在,当使用此宏时,您将不再手写let。此外,此宏目前不适用于声明多个参数的函数定义。但是修改它以使其也适用于那些也不会太难。

【讨论】:

  • 哇。我真的很喜欢你的想法。不过我选择了不同的答案,因为代码更容易理解(我还有很多 Clo​​jure 路要走……)
【解决方案3】:

我认为您在这里寻找的一般概念是 K-combinator。您可以在Fogus' library 中看到它的一个示例。你可以这样使用它:

(defn test2 []
  (println "start server")
  ((K (identity :return-value))
   (println "stop server")))

=> (test2)
start server
stop server
:return-value

对于这种情况,这似乎有点过于复杂,所以也许我们可以简化它(我不确定这个匿名函数是否“正式”是 k-combinator):

(defn test3 []
  (println "start server")
  ((fn [a b] a)
   (identity :return-value)
   (println "stop server")))

=> (test3)
start server
stop server
:return-value

由于字面量向量不是惰性的,你也可以先以同样的方式使用:

(defn test4 []
  (println "start server")
  (first
   [(identity :return-value)
    (println "stop server")]))

=> (test4)
start server
stop server
:return-value

不过,老实说,在实际代码中,我只会使用 let(就像您不想那样做),因为我的意图似乎更清晰:

(defn test5 []
  (println "start server")
  (let [result (identity :return-value)]
    (println "stop server")
    result))

=> (test5)
start server
stop server
:return-value

【讨论】:

    【解决方案4】:

    这就是 let 的用途!只是观察在试图避免它时出现的不必要的复杂性。

    这种情况是关于在返回第二个结果时必须执行的三个副作用。典型用例:数据库访问。

    【讨论】:

      【解决方案5】:

      这是一种“函数式”方式,不使用 let 构造:

      (defn apply-get-first [c f] (f) c)
      
      (defn p []
        (start
        (apply-get-first  (compute) 
                         #(end)))
      

      现在用 let:

      你可以这样做:

      (defn p[]
        (let [s (start)
              r (compute)
              e (end)]
          r))
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2022-01-03
        • 1970-01-01
        • 1970-01-01
        • 2012-08-18
        • 1970-01-01
        • 2016-02-22
        • 2021-06-03
        相关资源
        最近更新 更多