How to combine a let binding and an if statment in Clojure - clojure

I'm learning Clojure, and trying to figure out the language to write things concisely and clearly. I'm trying to conditionally do something with a value after getting a value (say from a database) from a function that either returns the value or nil. I'm currently using a let binding, and then an if statement. Here is something similar to what I have currently:
(defn foo [key]
(let [result (try-getting! key)]
(if result
(+ result 50)
50)))
I'm trying to figure out if there is a more consise way to do this, perhaps some sort of combined if-let binding? Is there a way to write this better?
Thanks!

There is form if-let for this:
(defn foo [key]
(if-let [result (try-getting! key)]
(+ result 50)
50))

Yes, in fact if-let is what you want here.
(if-let [result (try-getting! key)]
(+ result 50)
50)

Related

Return value from middle of `do` (Clojure)

I have a list of I/O functions to run in a game, but need to collect the value from a function somewhere in the middle of the do
(defn setup-steps [game-state]
(do (io/clear-screen)
(print-welcome-message)
(initial-setup) ;; value to be collected
(io/clear-screen)
(io/print-board game-state)))
Is there a smart way to return the value from somewhere in the middle of a do?
Down the line, I am using the return value of setup-steps to update an atom, like so:
(defn game-loop [game]
(while (:game-in-progress? #game)
;; Here is where I am using the results
(->> (s-io/setup-steps #game) (state/updater game))
(while (:game-in-progress? #game)
(->> (m-io/turn-steps #game) (state/updater game)))
(->> (eg-io/end-game-steps #game) (state/updater game)))
(eg-io/exit-game))
Where
(defn updater
"Updates the game state.
This is the only place where the game atom is modified."
[game update-params]
(swap! game merge update-params))
I'm sure you could write a macro for this, but I don't really understand macros yet.
And maybe I am thinking about this the wrong way... Is it more idiomatic to dow the swap!ing inside setup-steps?
Any reason you can't assign the result in a let and return it at the end of the function?
(defn setup-steps [game-state]
(io/clear-screen)
(print-welcome-message)
(let [v (initial-setup)] ;; value to be collected
(io/clear-screen)
(io/print-board game-state)
v))
EDIT: Got rid of the redundant do that Ryan Asensio mentioned.
Fundamentally the only way to do this is with let, as clartaq shows. But if you find this distasteful there are a number of ways you could wrap this up in a macro to make it prettier to look at and also more clear about what you're doing. Here is the simplest one, which I like to call returning:
(defmacro returning [x & more]
`(let [x# ~x]
(do ~#more)
x#))
(defn foo []
(x)
(y)
(returning (z)
(a)
(b)))
The do in returning is of course superfluous, but I think it's still useful to emphasize that more is evaluated only for side effects.

Conditional "assignment" in functional programming

I am programming something that doesn't have side-effects, but my code is not very readable.
Consider the following piece of code:
(let [csv_data (if header_row (cons header_row data_rows) data_rows)]
)
I'm trying to use csv_data in a block of code. What is a clean way of conditioning on the presence of a header_row? I've looked at if-let, but couldn't see how that could help here.
I have run into similar situations with functional for-loops as well where I'm binding the result to a local variable, and the code looks like a pile of expressions.
Do I really have to create a separate helper function in so many cases?
What am I missing here?
Use the cond->> macro
(let [csv_data (cond->> data_rows
header_row (cons header-row)]
)
It works like the regular ->> macro, but before each threading form a test expression has to be placed that determines whether the threading form will be used.
There is also cond->. Read more about threading macros here: Official threading macros guide
First, don't use underscore, prefer dashes.
Second, there is nothing wrong with a little helper function; after all, this seems to be a requirement for handling your particular data format.
Third, if you can change your data so that you can skip those decisions and have a uniform representation for all corner cases, this is even better. A header row contains a different kind of data (column names?), so you might prefer to keep them separate:
(let [csv {:header header :rows rows}]
...)
Or maybe at some point you could have "headers" and "rows" be of the same type: sequences of rows. Then you can concat them directly.
The ensure-x idiom is a very common way to normalize your data:
(defn ensure-list [data]
(and data (list data)))
For example:
user=> (ensure-list "something")
("something")
user=> (ensure-list ())
(())
user=> (ensure-list nil)
nil
And thus:
(let [csv (concat (ensure-list header) rows)]
...)
i would propose an utility macro. Something like this:
(defmacro update-when [check val-to-update f & params]
`(if-let [x# ~check]
(~f x# ~val-to-update ~#params)
~val-to-update))
user> (let [header-row :header
data-rows [:data1 :data2]]
(let [csv-data (update-when header-row data-rows cons)]
csv-data))
;;=> (:header :data1 :data2)
user> (let [header-row nil
data-rows [:data1 :data2]]
(let [csv-data (update-when header-row data-rows cons)]
csv-data))
;;=> [:data1 :data2]
it is quite universal, and lets you fulfill more complex tasks then just simple consing. Like for example you want to reverse some coll if check is trueish, and concat another list...
user> (let [header-row :header
data-rows [:data1 :data2]]
(let [csv-data (update-when header-row data-rows
(fn [h d & params] (apply concat (reverse d) params))
[1 2 3] ['a 'b 'c])]
csv-data))
;;=> (:data2 :data1 1 2 3 a b c)
update
as noticed by #amalloy , this macro should be a function:
(defn update-when [check val-to-update f & params]
(if check
(apply f check val-to-update params)
val-to-update))
After thinking about the "cost" of a one-line helper function in the namespace I've came up with a local function instead:
(let [merge_header_fn (fn [header_row data_rows]
(if header_row
(cons header_row data_rows)
data_rows))
csv_data (merge_header_fn header_row data_rows) ]
...
<use csv_data>
...
)
Unless someone can suggest a more elegant way of handling this, I will keep this as an answer.

Conditionally adding to a collection

I want to add something to a collection based on a condition and leave it alone otherwise.
I found myself writing something like this:
(defn make-zoo
[zoo has-ice]
(let [zoo (if has-ice (conj zoo "penguins") zoo)]
zoo))
(make-zoo ["tigers"] false) ;["tigers"]
(make-zoo ["polar bears"] true) ;["polar bears" "penguins"]
I'm pretty new to Clojure, but this seems like a clunky solution for a common operation. Is there a more elegant way to address this?
We can simplify make-zoo using the cond-> macro, a conditional derivative of the -> threading macro:
(defn make-zoo [zoo has-ice]
(cond-> zoo, has-ice (conj "penguins")))
One simplification is to just leave out the let statement:
(defn make-zoo
[zoo has-ice]
(if has-ice
(conj zoo "penguins")
zoo))
(make-zoo ["tigers"] false) => ["tigers"]
(make-zoo ["polar bears"] true) => ["polar bears" "penguins"]

clojure, dynamic-var, need macro?

Background
I'm trying to write the following function:
(defn cache-get [dynamic-var k func]
(if-let [v (get-in dynamic-var k)]
v
(let [ans (func)]
(set! dynamic-var (assoc-in dvar k ans))
ans)))
The arguments to cache-get are:
dynamic-var, something like (def ^:dynamic *my-local-cache*)
k, a key, something like :foo
func, a function, to be called if the value does not exist
The behavior of the function is:
if the key k exists, just return the value
if the key k does not exist, (1) compute it by calling (func) and (2) store it in the cache
Problem I am facing
The problem I am facing is that I apparently can not do (set! dynamic-var ...), because the function has no idea I will be passing it a dynamically bound var and thinks dynamic-var is a regular variable.
My current option:
Rewrite the function as a macro -- which is fine, but I prefer to not use macros unless absolutely necessary.
My question: is there a way to implement cache-get without using macros?
[Also, I want this to be a dynamic-var, not an atom]
Thanks!
Edit 1
I try suggestion of var-set. I get the following error:
(def ^:dynamic *query-ctx* {})
(defn cache-get [dynamic-var k func]
(if-let [v (get-in dynamic-var k)]
v
(let [ans (func)]
(var-set dynamic-var (assoc-in dynamic-var k ans))
ans)))
(cache-get *query-ctx* [:foo-key] (fn [] :foo-value))
;; clojure.lang.PersistentArrayMap cannot be cast to clojure.lang.Var
To answer the question: you want var-set instead of set!.
Quick question: if you're passing in the variable, do you really need a dynamic var?
[Also, I want this to be a dynamic-var, not an atom]
Can you expand on that? It does seem like an atom would be a much more logical choice. Do you need a thread-specific version of that cache?
Update: example call:
(cache-get #'*query-ctx* [:foo-key] (fn [] :foo-value))

Clojure: pass value if it passes predicate truth test

Is it possible to remove the let statement / avoid the intermediate 'x' in the following code?:
(let [x (f a)]
(when (pred? x) x))
I bumped into this problem in the following use case:
(let [coll (get-collection-somewhere)]
(when (every? some? coll) ; if the collection doesn't contain nil values
(remove true? coll))) ; remove all true values
So if the collection is free of nil values, only not-true values remain, like numbers, strings, or whatever.
So, I'm looking for something like this:
(defn pass-if-true [x pred?]
(when (pred? x) x))
Assuming that you don't want to define that pass-if-true function, the best you can do is an anonymous function:
(#(when (every? some? %)
(remove true? %))
(get-collection-somewhere))
You could also extract the predicate and transformation into parameters:
(#(when (%1 %3) (%2 %3))
(partial every? some?)
(partial remove true?)
(get-collection-somewhere))
The let form is necessary to prevent your collection-building function from running twice:
(f a) or (get-collection-somewhere)
This is a typical idiom and you are doing it correctly.
Of course, you don't need the let if you already have the collection and are not building inside this expression.
However, you may wish to see when-let:
https://clojuredocs.org/clojure.core/when-let
It can save some keystrokes in some circumstances, but this isn't one of them.