sebi
sebi

Reputation: 1841

Clojure functions - returning value computed before the last statement

I have some tests written in clojure. This is a simple example:

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

I would like to return the result of "run-pvt-and-expect", but I need to execute other functions after it. How can I do this in clojure in a functional way (without using constructs like "let")? Thanks.

Note: I read this question and its answers but couldn't apply it to my case. Also, a comment asked for examples that were never given, so please consider this as an example...

Upvotes: 5

Views: 1186

Answers (5)

Shlomi
Shlomi

Reputation: 4756

here is a "functional" way, without using let constructs:

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

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

and now with let:

you could do it like this:

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

Upvotes: 1

Wojciech Winogrodzki
Wojciech Winogrodzki

Reputation: 412

This is what the let is for! Just observe the unnecessary complexity that arises when trying to avoid it.

The situation is about having three side effects that must be executed while returning the result of the second. Typical use case: database access.

Upvotes: 1

bmaddy
bmaddy

Reputation: 895

I think the general concept you're looking for here is the K-combinator. You can see an example of it in Fogus' library. You could use it like this:

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

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

That seems a little overly complex for this case, so maybe we can simplify it (I'm not sure if the anonymous function in this one is "officially" the k-combinator or not):

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

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

Since literal vectors aren't lazy, you could also use first in the same way:

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

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

Honestly though, in real code I'd just use let (like you don't want to do) because the intent seems clearer to me:

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

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

Upvotes: 3

Omri Bernstein
Omri Bernstein

Reputation: 1933

Well, I know of nothing built-in that you could use (other than let, which you don't want). But the beauty is that you could build it yourself.

One idea would be to make a "new" defn, where a :return keyword to the left of an expression signifies returning the value from that, not the last expression. A macro will be appropriate here, because we'll be building up a modified (defn ...) expression, which requires unevaluated code.

(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)))

This expands something like:

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

To something like:

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

For example, you could now do:

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

And now in a REPL:

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

(Yay!)

Or for your example, you would now do:

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

True, the macro uses let, but it works by automating the let expansion you would make if you were handwriting it each time. Meaning that now, when using this macro, you would no longer handwrite let. Also, this macro does not currently work on function definitions that declare multiple arities. But it wouldn't be too difficult to modify it to work for those as well.

Upvotes: 4

stonemetal
stonemetal

Reputation: 6208

You can do this using a finally block.

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

produces a result of 5 not 13.

Here is the relevant doc page. The releavant part is:

any finally exprs will be evaluated for their side effects.

Another option would be to write a macro like with-open to start and stop your server while returning the right results. Though that just moves the problem to how do you write the macro, since it would either need to use a let or the try finally construct above.

Upvotes: 8

Related Questions