(clj 3) Clojure’s ‘and’ and ‘or’ are weird (but not really)
Early in chapter 3 of the Brave and True-book the Boolean operators and
and or
are introduced:
Clojure uses the Boolean operators
or
andand
.or
returns either the first truthy value or the last value.and
returns the first falsey value or, if no values are falsey, the last truthy value.
This explanation is followed by some examples:
(or false nil :large_I_mean_venti :why_cant_I_just_say_large) ; => :large_I_mean_venti (or (= 0 1) (= "yes" "no")) ; => false (or nil) ; => nil
(and :free_wifi :hot_coffee) ; => :hot_coffee (and :feelin_super_cool nil false) ; => nil
What I found remarkable about this is that and
and or
do not return a boolean in all cases. Before I go into that, let’s back up a second and cover their basics in a little more depth first.
The basics of Clojure’s and
and or
After reading the book’s explanation of and
and or
‘s behavarior, I played around with them a bit and also read their official documentation. That lead me to coming up with a description of their behavior that works better for me:
- both
and
andor
return the last value they evaluate; -
or
stops evaluating at the first truthy value it evaluates, since anor
needs only one true value to evaluate to true; -
and
stops evaluating at the first falsy value it evaluates, since anand
needs only one false value to evaluate to false.
In Clojure only false
and nil
are falsy, everything else is truthy. So that allows us to construct the following examples:
(and true true) ; => true (and true :tree) ; => :tree (and true false true) ; => false (and true nil true) ; => nil
(or false true false) ; => true (or false :falcon false) ; => :falcon (or false false) ; => false (or false nil) ; => nil
Finally, there’s (and)
evaluating to true
, while (or)
evaluates to nil
, which is mentioned in their respective doc pages. Not exactly sure why they do that, but who am I to disagree?
They don’t return only true
or false
Since and
and or
are boolean operators, I figured they would always return either true
or false
. That is how you use them, right? Give them some expressions that evaluate either to true
or false
and then depending if you string them together with and
or or
, you get true
or false
back. And that turns out to not be the case: and
and or
return the result of their last evaluation. If that’s true
, you get true
back, but if it’s "tree"
or :falcon
, that’s what you get. So I started to think why these operators behaved that way.
A theoretical argument
Earlier in the Brave and True-book it explained that Clojure only has two types of things: literal representations of data structures (like numbers, maps, and vectors) and operations. With these two you create expressions (aka forms) and “Clojure evaluates every form to produce a value.”(p 91) Combine this with the basic idea of functional programming, i.e. functions as mathemetical functions, you expect functions - and that’s what the and
and or
operations are - to take input, transform it, and return the transformed input as their output.
Looking at it that way, in some intuitive conceptual way, it does make sense to me that and
and or
return the last form they evaluated instead of the last form they evaluated converted to either true
or false
. If you need that conversion, you can do that yourself. In a similar vein, Clojure’s println
returns nil
and the printing of the line is considered a side effect.
A practical argument
I’m ok with their being only a theoretical reason for this, keeping the language conceptually consistent. Yet I wondered if there was a practical usage for this behavior of and
and or
. A possible use I saw was that you could chain functions. For instance, with and
the first function could either return a truthy
value, making the and
return that value; or that first function would return false
or nil
, meaning the second function would be evaluated. And so on, until either a function returns something truthy or you get a false
or nil
from the last one.
So I created a very simple example:
(defn mult2_if_int [input] (and (if (integer? input) true) ; returns nil when the if-clause is false (* input 2) ) ) (mult2_if_int 4) ; => 8 (mult2_if_int "four") ; => nil
However, you can get the same behvarior by using if
and that does look more readable to me:
(defn mult3_if_int [input] (if (integer? input) (* input 3) ) ) (mult3_if_int 4) ; => 12 (mult3_if_int "four") ; => nil
That wouldn’t work as well with multiple conditions, but a quick search showed me that’s what Clojure’s cond
and case
are for. So doing the same as above with cond
, still with only one condition though:
(defn mult4_if_int [input] (cond (integer? input) (* input 4) :else nil) ) (mult4_if_int 2) ; => 8 (mult4_if_int "two") ; => nil
So for and
I have trouble seeing a use case for this behavior. For or
there is an example in the Clojure docs, using it to return a default value if the function returns nil
:
(or (mult2_if_int 4) "that was not a number") ; => 4 (or (mult2_if_int "four") "that was not a number") ; => "that was not a number"
Surely Python does something different
The language I’m most familiar with and thus my language of reference, is Python. And throughout my exploration of Clojure’s and
and or
, I kept thinking that surely Python does this differently. That is, until I checked:
True and True # => True True and "tree" # => 'tree' True and False and True # => False True and None and True # => None
False or True or False # => True False or "falcon" or False # => 'falcon' False or False # => False False or None # => None
And in the revelant Python docs, which I must say are excellent as always, I found:
Note that neither
and
noror
restrict the value and type they return toFalse
andTrue
, but rather return the last evaluated argument. This is sometimes useful, e.g., ifs
is a string that should be replaced by a default value if it is empty, the expressions or 'foo'
yields the desired value. Becausenot
has to create a new value, it returns a boolean value regardless of the type of its argument (for example,not 'foo'
producesFalse
rather than''
.)
And this behavior of not
is also present in Clojure:
(not "foo") ; => false
So I suppose the conclusion is that even though it may feel a bit weird to have and
and or
not only returning true
or false
, Clojure is not any weirder in this respect than some other languages.
As a postscript to this post, I’ll share some smaller things I noticed and/or learned about Clojure and vim.
Notes on Clojure
In a previous post (clj 1) I wrote I have a vague notion of what a “form” is, but wouldn’t be able to explain it. Well, chapter 3 of the Brave and True-book explains that “form” refers to valid code and that it will sometimes use “expression” as a synonym. I had actually read this before writing that earlier blog post, so that shows the value of re-reading.
When I created a new clj
-file to play around in with and
and or
and tried to evaluate a form, I got the following error:FileNotFoundException Could not locate clojure_noob/namespace__init.class or clojure_noob/namespace.clj on classpath.
Please check that namespaces with dashes use underscores in the Clojure file name. clojure.lang.RT.load (RT.java:456)
I fixed it by copy-pasting the namespace declaration of my core.clj
file to the top of the new file: (ns clojure-noob.core (:gen-class))
. I have no idea if that’s the proper way to use namespaces, but I’ll learn that in chapter 6 of the Brave and True-book.
It surprised me that after making changes to a function definition and using :Require
or Require!
to reload a namespace, I still first had to evaluate the function definition itself before I was able to use the updated version.
Reminder: function definitions go inside of a set of parantheses. Why? Everything is a list!
Notes on Vim
I switched to the gruvbox color scheme, since it provides higher contrast than minimalist.
It’s great to have my own cheatsheet to look things up.
If vim-fireplace can evaluate a form and add it to the buffer as a comment, I haven’t found out how. Might be non-trivial in Vim, because of Vim’s modes and command line.
I had to install vim-gtk3
to be able to copy from vim to my system’s clipboard.
Highlighting the line the cursor is in with set cursorline
looks great, until it seems to introduce lag in vertical cursor movement.
Instead of looking up the Clojure docs on the internet, I should hit K
. Thank you, vim-fireplace!
The parantheses help from vim-sexp is great, except when it isn’t. I still need to find the best way to add a matching paranthesis or bracket to an orphaned one. Perhaps delete orphan, then add the pair?
Notes on blogging
Writing these blog posts is slowing down my progress in the Brave and True-book significantly, but it is also forcing (encouraging?) me to engage deeper with Clojure. So for now, I’m seeing that as a good thing. Also, it can’t hurt to spend some extra time on the basics. I’ve heard too many experts say when asked how to become excellent at something: “basics, basics, basics”.