Truth and languages
The title of this paragraph sounds a bit postmodern. But here, we want to discuss the truth in Clojurescript and javascript.
Javascript’s conception of the truth is a bit surprising. See by yourself:
Boolean(0) // false
Boolean("0") // true
0 == "0" // true
0 == false // true
"0" == false // true
Boolean("") // false
Boolean([]) // true
Boolean({}) // true
Boolean(Math.sqrt(-1)) // false
In any “normal” programming language, 0 and "" should be truthy.
You could argue about Math.sqrt(-1)…
On the opposite, Clojure’s conception of the truth is completly well defined.
It is therefore very interesting to ask:
How
Clojurescripthandles the truth?
Clojurescript: a teacher about truth
Let’s look at some transpiled javascript code with KLIPSE in order to understand how clojurescript checks if something is true:
(defn check [x]
(if x "true" "false"))
You see in the transpiled javascript code that the x variable has been wrapped into into a call to the cljs.core.truth_ function.
Here is the code for cljs.core.truth_:
function cljs$core$truth_(x) {
return x != null && x !== false
}
This is how
clojurescriptteachesjavascriptwhat is true and what is not - in its own language!
And indeed, javascript is a good student
cljs$core$truth_(true) // true
cljs$core$truth_(false) // false
cljs$core$truth_(0) //true
cljs$core$truth_("") // true
cljs$core$truth_(null) // false
cljs$core$truth_(undefined) // false
cljs$core$truth_(NaN) // true
cljs$core$truth_(Math.sqrt(-1)) // true
Performances
It’s nice to have a truth wrapper. But what if you are in a performance sensitive environment and you want to use the native javascript truth system - in order to move faster?
Well, clojurescript provides a way to let the compiler know that you trust javascript: the ^boolean type hint.
Let’s see it in action with KLIPSE:
(defn check [^boolean x]
(if x "true" "false"))
By using the ^boolean type hint, you let the compiler know that x must be a boolean i.e. either true or false. In that case, it’s safe to trust javascript about the truth. There is no need to wrap the hinted variable into cljs.core.truth_.
And the clojurescript compiler is smart: it knows how to propagate type hints i.e. if you assign a hinted variable x into an unhinted variable y, then y is automatically hinted.
Let’s check it with a simple piece of code in KLIPSE:
(defn check [^boolean x]
(let [y x]
let(if y "true" "false")))
You see that y has not been wrapped.
Cool, isn’t it?
Dangers
With great power comes great responsibility
Let’s have a look at some interesting edge cases exposed by Mike Fikes involving the ^boolean type hint:
(defn f [^boolean b]
(loop [x b
n 0]
(cond
(= n 100000) "almost infinite loop"
(not x) (recur 0 (inc n))
:else :done)))
(f false)
What’s happened here?
Remember that in javascript, 0 is falsy.
bis declared as a booleanxis also considered as a boolean because of type propagation:xis not wrapped intocljs.core.truth_fis called with a boolean value:false- So far so good…
- But
fbreaks the contract as it assigns a non-boolean value0intox.
Therefore, x is not wrapped, and we are in a non-safe situation: a non-boolean value is handled by the javascript truth system.
Let’s follow, the flow of the loop for the first two iterations:
- First iteration:
x=falseandn=0;falseis falsy ➠recurwithx=0andn=1 - Second iteration:
x=0andn=1;0is falsy ➠recurwithx=0andn=2 - ….
- …
…
How to get the best of the two worlds?
Clojurescript provides a way to turn off type inference, using the ^any type hint.
Let’s add ^any to x and see how it solves our problem:
(defn f [^boolean b]
(loop [^any x b
n 0]
(cond
(= n 100000) "almost infinite loop"
(not x) (recur 0 (inc n))
:else :done)))
(f false)
Hourra! x has been wrapped again and we are safe.
And obviously, it solves the infinite loop issue:
(defn f [^boolean b]
(loop [^any x b
n 0]
(cond
(= n 100000) "almost infinite loop"
(not x) (recur 0 (inc n))
:else :done)))
(f false)
Clojurescript rocks!