(? & bod)
With two or three arguments ?
behaves like if
(is :ok (? (pos? 1) :ok))
(isnt (? (pos? 0) :ok))
(is :ok (? (pos? 1) :ok :ko))
(is :ko (? (pos? 0) :ok :ko))
It can also bind some value like if-let
does
(def m {:a 2 :b 3}) ; used in following examples
(is 4
(? [a (get m :a)]
(+ a a)
:fail))
But the ?
macro can deal with several bindings (if-let
do not).
I need to check but I'm not sure that existing clojure’s implementations of cond-let
can do that properly.
(is 5
(? [a (get m :a)
b (get m :b)]
(+ a b)
:fail))
We can destructure
(is 5
(? [{:keys [a b]} m]
(+ a b)))
But this time it fails if an inner binding is nil
(isnt (? [{:keys [a c]} m]
(+ a c)))
The ?
macro can be used like cond
too
(let [f (fn [x]
(? (pos? x) [:pos x]
(neg? x) [:neg x]
:zero))] ;; unlike cond we do not need the :else keyword before the default case
(is (f -1)
[:neg -1])
(is (f 3)
[:pos 3])
(is (f 0)
:zero))
Even better, it can be used like cond-let
(let [f (fn [m]
(?
;; case 1
;; if m contains? a :foo key we bind its value to the symbol 'foo and return it
[foo (:foo m)] foo
;; case 2
[_ (:baz m) ;; checking that m is containing a :baz key
bar (:bar m)] ;; if yes we try to bind the :bar value of m to the symbol 'bar
;;then return it
bar
;; bottom case
[:fails m]))]
(is 1 (f {:foo 1 :bar 2}))
(is 2 (f {:bar 2 :baz 3}))
(is [:fails {:some :thing}]
(f {:some :thing})))
Those two flavors of let
/cond
(if-let
/cond-let
) can be mixed together
(defn mix-test [x]
(? ;; the first case do not bind its return value (like if)
(number? x)
(? (pos? x) [:pos x]
(neg? x) [:neg x]
:zero)
;; the second case is like a multi binding if-let, it tries to bind two values
[a (get x :a)
b (get x :b)] (+ a b)
;; if those two cases have failed we are printing something
(println "mix-test has failed")))
In fact if you think about it you realize that the ?
macro can behave pretty much like let
.
All let
forms that do not bind anything to nil
can be replaced by the ?
macro
(? [a 1 b 2]
(+ a b))
This is fine but sometimes I like to be able to bind things to nil
!
In fact the ?
macro has a way to do this
(? [m {:a 1 :b 2}
a (get m :a)
?c (get m :c)] ;; c is prefixed by ? meaning that is can be falsy
(? c [:a+c (+ a c)]
[:only :a a]))
Those prefixed symbols can be used also in destructuring patterns
(? [{:keys [a ?c]} {:a 1 :b 2}]
(? c [:a+c (+ a c)]
[:only :a a]))
So we cover the whole let
scope now
There is another thing that can be desirable in our programs.
It is to throw meaningful runtime errors, in clojure we sometimes have to chase nil
in a complex execution.
Which is not always easy nor pleasant.
The ?
macro is letting you prefix bindings that can never be falsy with !
'(? [!a (get {} :a)] :ok)
prints
strict binding failure:
a
(get {} :a)
(let [f (fn [m]
(? [!a (get m :a) ;; m has to have an :a key
b (get m :b)] ;; then we try to find a :b key
;; if the :b key exists in m we return a and b
[:a-and-b a b]
;; else we fail
:fail))]
(is (f {:a 1 :b 2})
[:a-and-b 1 2])
(is (f {:a 1})
:fail)
(throws (f {:c 3})))
Like the ‘?’ prefix the ‘!’ prefix can be used in destructurations
(let [m {:a 1 :b 2}]
(? [{:keys [!a b ?c]} m]
(list a b c)
:fail))
With two or three arguments `?` behaves like `if` ```clojure (is :ok (? (pos? 1) :ok)) (isnt (? (pos? 0) :ok)) (is :ok (? (pos? 1) :ok :ko)) (is :ko (? (pos? 0) :ok :ko)) ``` It can also bind some value like `if-let` does ```clojure (def m {:a 2 :b 3}) ; used in following examples (is 4 (? [a (get m :a)] (+ a a) :fail)) ``` But the `?` macro can deal with several bindings (`if-let` do not). I need to check but I'm not sure that existing clojure’s implementations of `cond-let` can do that properly. ```clojure (is 5 (? [a (get m :a) b (get m :b)] (+ a b) :fail)) ``` We can destructure ```clojure (is 5 (? [{:keys [a b]} m] (+ a b))) ``` But this time it fails if an inner binding is `nil` ```clojure (isnt (? [{:keys [a c]} m] (+ a c))) ``` The `?` macro can be used like `cond` too ```clojure (let [f (fn [x] (? (pos? x) [:pos x] (neg? x) [:neg x] :zero))] ;; unlike cond we do not need the :else keyword before the default case (is (f -1) [:neg -1]) (is (f 3) [:pos 3]) (is (f 0) :zero)) ``` Even better, it can be used like `cond-let` ```clojure (let [f (fn [m] (? ;; case 1 ;; if m contains? a :foo key we bind its value to the symbol 'foo and return it [foo (:foo m)] foo ;; case 2 [_ (:baz m) ;; checking that m is containing a :baz key bar (:bar m)] ;; if yes we try to bind the :bar value of m to the symbol 'bar ;;then return it bar ;; bottom case [:fails m]))] (is 1 (f {:foo 1 :bar 2})) (is 2 (f {:bar 2 :baz 3})) (is [:fails {:some :thing}] (f {:some :thing}))) ``` Those two flavors of `let`/`cond` (`if-let`/`cond-let`) can be mixed together ```clojure (defn mix-test [x] (? ;; the first case do not bind its return value (like if) (number? x) (? (pos? x) [:pos x] (neg? x) [:neg x] :zero) ;; the second case is like a multi binding if-let, it tries to bind two values [a (get x :a) b (get x :b)] (+ a b) ;; if those two cases have failed we are printing something (println "mix-test has failed"))) ``` In fact if you think about it you realize that the `?` macro can behave pretty much like `let` . All `let` forms that do not bind anything to `nil` can be replaced by the `?` macro ```clojure (? [a 1 b 2] (+ a b)) ``` This is fine but sometimes I like to be able to bind things to `nil`! In fact the `?` macro has a way to do this ```clojure (? [m {:a 1 :b 2} a (get m :a) ?c (get m :c)] ;; c is prefixed by ? meaning that is can be falsy (? c [:a+c (+ a c)] [:only :a a])) ``` Those prefixed symbols can be used also in destructuring patterns ```clojure (? [{:keys [a ?c]} {:a 1 :b 2}] (? c [:a+c (+ a c)] [:only :a a])) ``` So we cover the whole `let` scope now There is another thing that can be desirable in our programs. It is to throw meaningful runtime errors, in clojure we sometimes have to chase `nil` in a complex execution. Which is not always easy nor pleasant. The `?` macro is letting you prefix bindings that can never be falsy with `!` ```clojure '(? [!a (get {} :a)] :ok) ``` prints ``` strict binding failure: a (get {} :a) ``` ```clojure (let [f (fn [m] (? [!a (get m :a) ;; m has to have an :a key b (get m :b)] ;; then we try to find a :b key ;; if the :b key exists in m we return a and b [:a-and-b a b] ;; else we fail :fail))] (is (f {:a 1 :b 2}) [:a-and-b 1 2]) (is (f {:a 1}) :fail) (throws (f {:c 3}))) ``` Like the ‘?’ prefix the ‘!’ prefix can be used in destructurations ```clojure (let [m {:a 1 :b 2}] (? [{:keys [!a b ?c]} m] (list a b c) :fail)) ```
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close