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 Clojurescript handles 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 clojurescript teaches javascript what 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.

  1. b is declared as a boolean
  2. x is also considered as a boolean because of type propagation: x is not wrapped into cljs.core.truth_
  3. f is called with a boolean value: false
  4. So far so good…
  5. But f breaks the contract as it assigns a non-boolean value 0 into x.

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:

  1. First iteration: x=false and n=0; false is falsy ➠ recur with x=0 and n=1
  2. Second iteration: x=0 and n=1 ; 0 is falsy ➠ recur with x=0 and n=2
  3. ….


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!