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 [shooter (who-shot-jr 5)] shooter 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.
;;; 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 ;; Note that this implementation short-curcuits on false so that the prn form ;; is _not_ evaluated in the below form. (if-let* [a false b (prn "Do we evaluate this?")] true false) ;;=> false
;; (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
;; Destructuring maps (def x {:it 1 :that 2}) (def y {:that 2} (if-let [{value :it} x] value "Not found") ;;=> 1 (if-let [{value :it} nil] value "Not found") ;;=> "Not found" (if-let [{value :it} false] value "Not found") ;;=> "Not found" (if-let [{value :it} y] value "Not found") ;;=> nil (if-let [{value :it} {}] value "Not found") ;;=> nil (if-let [{value :it} 1] value "Not found") ;;=> nil
;; if-let multiple bindings version (defmacro if-let* ([bindings-vec then] `(if-let* ~bindings-vec ~then nil)) ([bindings-vec then else] (if (seq bindings-vec) `(let ~bindings-vec (if (and ~@(take-nth 2 bindings-vec)) ~then ~else))))) (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 ;; Note that this implementation does _not_ short-circuit on falsey forms. (if-let* [a false b (prn "Do we evaluate this?")] true false) ;; "Do we evaluate this?" ;;=> false
;; Check if the value is true print it otherwise not. (def log-output "") ;; Assume log-output could be the result of another operation like ;; (def log-output (Gl/ShaderInfoLog shader)) ;; The Following will only print, when logout is not empty. (if-let [out (not-empty logout)] (println out))
;; fun of if-let (defn some-condition [data] true) (let [result (some-condition "ABC")] (if (true? result) "Success" "Failure")) ;; => "success" ;; if-let in Action (if-let [result (some-condition "ABC")] "Success" "Failure") ;; => "success"
The difference between when-let and if-let is that when-let doesn't have an else clause and and also accepts multiple forms so you don't need to use a (do...).