4

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

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

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

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

4

5 回答 5

7

您可以使用 finally 块来执行此操作。

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

产生 5 而不是 13 的结果。

这是相关的文档页面。 相关部分是:

将评估任何 finally expr 的副作用。

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

于 2013-06-21T13:43:52.730 回答
4

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

一个想法是创建一个 "new" 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. 此外,此宏目前不适用于声明多个参数的函数定义。但是修改它以使其也适用于那些也不会太难。

于 2013-06-21T17:38:56.187 回答
3

我认为您在这里寻找的一般概念是 K 组合器。您可以在Fogus 的库中看到它的一个示例。你可以像这样使用它:

(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
于 2013-06-21T18:36:37.747 回答
1

这是一种“功能性”的方式,不使用 let 构造:

(defn apply-get-first [c f] (f) c)

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

现在让:

你可以这样做:

(defn p[]
  (let [s (start)
        r (compute)
        e (end)]
    r))
于 2013-06-21T16:19:00.197 回答
1

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

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

于 2013-06-22T18:08:59.023 回答