Showing posts with label if. Show all posts
Showing posts with label if. Show all posts

Tuesday, March 01, 2011

Clojure: if-let and when-let

I'm a fan of if-let and when-let. Both can be helpful in creating succinct, readable Clojure code. From the documentation:
If test is true, evaluates then with binding-form bound to the value of test, if not, yields else
A quick example should demonstrate the behavior.
user=> (if-let [a :anything] a "no")    
:anything
user=> (if-let [a nil] a "no")
"no"
user=> (if-let [a false] a "no")
"no"
The above example demonstrates a simple case where a truthy value causes the "then" form to be returned, and a falsey value (nil or false) causes the "else" form to be returned. The example also shows that "a" is bound to the value :anything and can be used within the "then".

The following example shows that you can put code in the "then" and "else" statement's as well.
user=> (if-let [a 4] (+ a 4) (+ 10 10)) 
8
user=> (if-let [a nil] (+ a 4) (+ 10 10))
20
Just like above, when "a" is truthy the value is bound and it can be used as desired in the "then".

The when-let function behaves basically the same way, except there is no "else".
user=> (when-let [a 9] (+ a 4))  
13
user=> (when-let [a nil] (+ a 4))
nil
user=> (when-let [a 9] (println a) (+ a 4))
9
13
The example demonstrates that then "then" case is very similar to if-let; however, if the test fails the when-let function simply returns nil. The last when-let example also shows how it slightly differs from an if-let: you can pass as many forms to the "then" as you'd like. If the test evaluates to true, all of the additional forms will be evaluated.

There are a few things worth knowing about if-let and when-let with respect to the bindings. Both if-let and when-let require a vector for their bindings, and there must be exactly two forms in the binding vector. The following example shows Clojure's response if you choose not to follow those rules.
user=> (when-let (a 9) (println a))
java.lang.IllegalArgumentException: when-let requires a vector for its binding (NO_SOURCE_FILE:0)
user=> (when-let nil (println "hi"))
java.lang.IllegalArgumentException: when-let requires a vector for its binding (NO_SOURCE_FILE:0)
user=> (when-let [a 9 b nil] (println a b))
java.lang.IllegalArgumentException: when-let requires exactly 2 forms in binding vector (NO_SOURCE_FILE:0)
It's nice to know if-let and when-let, but they aren't exactly hard concepts to follow. Once someone points them out to you, I expect you'll be able to easily use them in your code without much effort.

However, what is the impact of destructuring on if-let and when-let? I know what I expected, but I thought it was worth a quick trip to the REPL to make sure my assumptions were correct. The following code shows that as long as the value being bound is truthy the "then" will be executed - destructuring values have their normal behavior and do no effect execution flow.
user=> (when-let [{a :a} {:a 1}] [a])
[1]
user=> (when-let [{a :a} {}] [a])
[nil]
user=> (when-let [{a :a} {:a false}] [a])
[false]
user=> (when-let [{a :a} nil] [a])
nil
As you would expect the when-let is consistent and the destructuring behavior is also consistent.

if-let and when-let are good, give them a shot.

Saturday, February 26, 2011

Clojure: Truthy and Falsey

Truthy and Falsey are important concepts in Clojure. The best description I've seen is in The Joy of Clojure, go buy it.

Depending on your language background you may have very different ideas on what evaluates to true and what evaluates to false. In Clojure, the answer to this is very straightforward.
Both nil and false are treated as "false" and everything else is treated as true.
Let's hit the REPL.
user=> (if true "yes" "no")
"yes"
user=> (if false "yes" "no")
"no"
Okay, that much should be obvious. However, it's important to note that nil is also considered false, which is why we say:
In Clojure nil and false are considered "false" and therefore we say they are both "falsey".
Another quick trip to the REPL to verify that nil is falsey.
user=> (if nil "yes" "no")  
"no"
As expected, the else is evaluated.

There are other values that are considered false in other languages, this does not apply to clojure.
All non-falsey values are considered "truthy" and evaluate as such.
Here are some common examples of truthy values in Clojure that are falsey in other languages.
user=> (if -1 "still true" "false")
"still true"
user=> (if 0 "still true" "false")
"still true"
user=> (if [] "still true" "false")
"still true"
user=> (if (list) "still true" "false")
"still true"
As it says in The Joy of Clojure: Every[thing] is "true" all the time, unless it is nil or false.

Sunday, July 22, 2007

Ruby: Multiple ifs (unless)

I recently ran into some code similar to the snippet below.

item.currency if item.currency != :usd unless item.nil?

Based on reading the code I assumed it worked as expected; however, having never actually tried it myself I decided to hit irb for a moment with the following code.

p 3 unless p 2 unless p 1

Sure, I'm not using if, but if and unless have the same precedence, so I thought the example was good enough.

p 3 unless p 2 unless p 1
# >> 1
# >> 2
# >> 3

The output shows the execution order: The rightmost if (or unless) is evaluated first and then it moves to the next conditional immediately to it's left.

Of course, the statement could be rewritten simply using ||.

item.currency unless item.nil? || item.currency == :usd

Due to short circuit evaluation neither statement executes item.currency != :usd if item.nil?.