Reputation: 1841
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
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
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
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
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
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