if-let

added
1.0

ns
clojure.core

type
macro

(if-let bindings then) (if-let bindings then else & oldform)

bindings => binding-form test

If test is true, evaluates then with binding-form bound to the value of 
test, if not, yields else

                user=> (defn sum-even-numbers [nums]
         (if-let [nums (seq (filter even? nums))]
           (reduce + nums)
           "No even numbers found."))
#'user/sum-even-numbers

user=> (sum-even-numbers [1 3 5 7 9])
"No even numbers found."

user=> (sum-even-numbers [1 3 5 7 9 10 12])
22

            
                (if-let [x false y true]
     "then"
     "else")
;; java.lang.IllegalArgumentException: if-let requires exactly 2 forms in binding vector (NO_SOURCE_FILE:1)
;; see if-let* below

(defn if-let-demo [arg]
  (if-let [x arg]
    "then"
    "else"))

(if-let-demo 1) ; anything except nil/false
;;=> "then"
(if-let-demo nil)
;;=> "else"
(if-let-demo false)
;;=> "else"

            
                ;; This macro is nice when you need to calculate something big. And you need 
;; to use the result but only when it's true:

(if-let [life (meaning-of-life 12)]
   life
   (if-let [origin (origin-of-life 1)]
      origin
      (if-let [shot (who-shot-jr 5)]
         block-sol
\t 42)))

;; As you can see in the above example it will return the answer 
;; to the question only if the answer is not nil. If the answer
;; is nil it will move to the next question. Until finally it
;; gives up and returns 42.
            
                ;; See examples for "if" explaining Clojure's idea of logical true
;; and logical false.
            
                ;;; with destructuring binding

;; successful case
(if-let [[w n] (re-find #"a(\\d+)x" "aaa123xxx")]
  [w n]
  :not-found)  ;=> ["a123x" "123"]

;; unsuccessful case
(if-let [[w n] (re-find #"a(\\d+)x" "bbb123yyy")]
  [w n]
  :not-found) ;=> :not-found

;; same as above
(if-let [[w n] nil]
  [w n]
  :not-found) ;=> :not-found

;; on Map
(if-let [{:keys [a b]} nil]
  [a b]
  :not-found) ;=> :not-found

            
                ;; Note that the binding only extends to the then form, not to the else:
user=> (if-let [x nil] "then" x)
CompilerException java.lang.RuntimeException: Unable to resolve symbol:
x in this context, compiling: ...
            
                ;; Works well with collections

=> (def x {:whatever 1})

=> (if-let [value (:whatever x)] value "Not found")
1

=> (if-let [value (:no-match x)] value "Not found")
"Not found"
            
                ;; if-let multiple bindings version
;; Edited: Else branch did not work with expressions.

(defmacro if-let*
  ([bindings then]
   `(if-let* ~bindings ~then nil))
  ([bindings then else]
   (if (seq bindings)
     `(if-let [~(first bindings) ~(second bindings)]
        (if-let* ~(drop 2 bindings) ~then ~else)
        ~else)
     then)))

(if-let* [a 1
          b (+ a 1) ]
          b)
;;=> 2

(if-let* [a 1
           b (+ a 1)
           c false] ;;false or nil - does not matter
           b
           a)

;;=> 1
            
                ;; (if-let [definition condition] then else):
;; if the value of condition is truthy, then that value is assigned to the definition, 
;; and "then" is evaluated.
;; Otherwise the value is NOT assigned to the definition, and "else" is evaluated.

;; Although you can use this structure with booleans, 
;; there's not much point unless you only want to
;; use the resulting boolean if it's true - as evidenced in the first example below.
;; if-let is mostly useful when checking for nil.

;; In this first example if Clare is old, it outputs "Clare is old".
;; (the let part of the statement is rather pointless, 
;; as the definition old-clare-age is never used).

(def clare-age 47)
(if-let [old-clare-age (> clare-age 100)] 
  "Clare is old" 
  "Clare is not old")
;;=> Clare is not old

;; In the next two examples, it only outputs Clare's age if it is valid (ie not nil)

(def clare-age nil)
(if-let [valid-clare-age clare-age] 
  (str "Clare has a valid age: " valid-clare-age) 
  "Clare's age is invalid")
;;=> Clare's age is invalid

(def clare-age 47)
(if-let [valid-clare-age clare-age] 
  (str "Clare has a valid age: " valid-clare-age) 
  "Clare's age is invalid")
;;=> Clare has a valid age: 47