Reputation: 14334
I have a token scanner that simply returns nil
for characters I'm not interested in. Rather than conj
the nils to my token vector and then later stripping them all out, I want to simply not add them.
I'm using
;; dont conjoin if value false
(defn condj [v val]
(cond-> v, val (conj val)))
to do this. Is there a specific operator or a more concise implementation?
Upvotes: 4
Views: 1724
Reputation: 2104
Consider using into
instead of conj
:
(into [1 2 3] nil) ;=> [1 2 3]
(into [1 2 3] [4]) ;=> [1 2 3 4]
Note: the downside is you must return results in a sequence which adds a bit of overhead. However, if you forget to do this you get an error, it makes it easy to extend the logic when you want to append more than one item, the code is easily understandable and doesn't require any custom functions to be created.
Upvotes: 0
Reputation: 4901
I believe you can use transducers for this. They are explained here. Our reducing function is conj
and we construct a transducer (remove nil?)
that turns this function into one that will ignore nil
:
(def condj ((remove nil?) conj))
Note that remove
is the opposite of filter
. We can also implement condj
using (filter some?)
, some? being a function that is true for any value except nil
:
(def condj ((filter some?) conj))
It seems to work:
user=> (condj [3 4 5] 9)
[3 4 5 9]
user=> (condj [3 4 5] nil)
[3 4 5]
user=> (condj [3 4 5] false)
[3 4 5 false]
Upvotes: 5
Reputation: 29958
I like the cond->
version and often use that to avoid repetition in the if
version. Don't forget to be explicit about false
values, though. I also like to use Plumatic Schema to be explicit about the data shape entering and leaving the function:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require
[schema.core :as s]))
(s/defn condj :- [s/Any]
"Conjoin an item onto a vector of results if the item is not nil."
[accum :- [s/Any]
item :- s/Any]
(cond-> accum
(not (nil? item)) (conj item)))
(dotest
(let [result (-> []
(condj :a)
(condj :2)
(condj false)
(condj "four")
(condj nil)
(condj "last"))]
; nil is filtered but false is retained
(is= result [:a :2 false "four" "last"])))
You may also be interested in another version using my favorite library:
(s/defn condj :- [s/Any]
"Conjoin an item onto a vector of results if the item is not nil."
[accum :- [s/Any]
item :- s/Any]
(cond-it-> accum
(not-nil? item) (append it item)))
For more complicated forms, using the placeholder symbol it
makes it explicit where the value is being threaded. I also like the convenience functions not-nil?
and append
since they also make the intent of the code plainer.
Upvotes: 3
Reputation: 45750
I think the overly simplified approach is the cleanest here (it's also slightly more concise):
(defn condj [v val]
(if val (conj v val) v))
I find that this is much easier to understand quickly. The only downside is v
is duplicated since it isn't being threaded, but that's not a big loss in such a simple function.
Upvotes: 3