ClojureScript is weakly typed!

clojurescript

#1

I just realized ClojureScript is weakly typed, and I’m really disappointed and bummed out.

(+ 1 "1")
=> "11"

I’ve always considered this the worst part of JavaScript. I find weakly typed languages do in fact create more bugs, and have higher defect rates. While I don’t believe statically checked languages do, the difference between strong and weak is pretty important to me.

Its also a huge difference from Clojure. I’m surprised I never heard of it before. What do other make of this?

Regards


#2

By the way, this came as a shock to me, because I feel I’ve actually heard many times that ClojureScript was strongly typed, and had it listed as an advantage over JavaScript. Even Eric Normand says so here:

https://lispcast.com/debugging-clojurescript/

Finally, ClojureScript avoids lots of bugs by virtue of its more functional semantics. ClojureScript is immutable by default. It is not weakly typed. And the language is more thought out.

The emphasis is mine, but in fact, ClojureScript is weakly typed.

Does anyone know, is there like a compiler flag to have strong run-time checks be auto-inserted to make it strongly typed?


#3

To be fair, weak/strong typing is a spectrum rather than a 2-slots classification, nor does it have a unique definition. In my view finding one operation accepting disparately-typed arguments without an error is not enough to definitely classify a language as weakly-typed: there are still plenty of operations in ClojureScript that will yield an error if applied to the wrong types, e.g treating a number as a function.

To me the example you describe is more an instance of “Clojure is written for correct programs” or “Clojure embraces the host platform” or just a bug than proof of weak-typing. Of course, our disagreement may stem from you having a stricter definition of strong typing than I do, but then I would question the relevance of that definition.


#4

Upcoming releases aim to tackle part of this problem though: https://github.com/clojure/clojurescript-site/blob/news-next/content/news/2018-09-01-release.adoc

In general, any inferred types will automatically flow from function bodies outward, such as an inferred numeric type in the following example:

(defn foo [x]
   (+ x 3))

If foo is used in a context where types are checked, as in

(+ (foo 1) "a")

you will now see a warning that properly reflects the type:

WARNING: cljs.core/+, all arguments must be numbers, got [number string] instead

Previously, without a type hint on foo, the compiler would produce a warning that indicates a type of any instead of number.


#5

Besides strong/weak typing there is static/dynamic typing. https://hackernoon.com/i-finally-understand-static-vs-dynamic-typing-and-you-will-too-ad0c2bd0acc7 maybe a static typed language like Elm better suits your needs. I really don’t mind your example much, and really like closure for generic functions over sets, compared to java where you have to explicitly type everything, making it much harder to reuse code.


#6

Writing obviously bad code like that will emit a ClojureScript compiler warning. And + is only ever used for numerics, str is for string concatenation - which is why probably people rarely encounter this behavior.

In the end, ClojureScript numerics bottom out in JavaScript numerics because of performance and that’s the tradeoff.


#7

As a reminder for myself and others, here’s all the ClojureScript functions (that I could find) which will do implicit type coercion (aka weak typing) similar to JavaScript:

+
-
*
/
mod
inc
dec
>
<
<=
>=
bit-and
bit-or
bit-not
bit-xor
bit-and-not
bit-clear
bit-flip
bit-set
bit-test
bit-shift-left
bit-shift-right
unsigned-bit-shift-right
"All JS interop, such as the ones in Math/"

Functions which depend on these might also suffer from weak typing.

Equality (=, ==, not, not=, etc.) and logical (and, or, etc.) functions in ClojureScript are strongly typed in ClojureScript, as opposed to JavaScript.

Here’s an example of the quirks of weak typing:

cljs.user=> (< 43 42)
false
cljs.user=> (< ["43"] [42])
true

cljs.user=> (inc "1")
"11"

cljs.user=> (dec "11")
10

cljs.user=> (+ "1" 1)
"11"

cljs.user=> (- "1" 1)
0

I’m not dropping the ball on ClojureScript, I still love it, but it seems it being strongly typed similar to Clojure is a myth. I think people should be aware of that, its something to watch out for.


#8

I define weak typing as a language which will perform automatic type coercion. I wouldn’t mind + having an overload of (+ "a" "b") and (+ 1 2), but I mind implicit type coercion rules which will allow (+ "a" 1) to work.

I agree, it’s a spectrum, and ClojureScript is better than JavaScript in that regard. That said, some language are 100% on one side of that spectrum, and to my knowledge, Clojure is 100% strongly typed. I don’t know of any implicit type coercion in Clojure core.

This would probably be the best solution. Keep the performance, but warn on its usage. The current warning isn’t powerful enough to warn when variables are used, so hopefully this inference flow will cover those edge cases.

Thanks for the suggestion. I have no issue with dynamic typing though. Strong vs Weak is orthogonal to Static vs Dynamic types though. Clojure is strongly typed and dynamically typed. ClojureScript, to my surprise, is weakly typed and dynamically typed. You can read more about it here: https://stackoverflow.com/a/2351203/172272

Thanks @dnolen for the justification. With more powerful type inference that can flow through variables, I should be satisfied, and the performance will remain. Otherwise, I personally would appreciate a compiler flag to add run-time checks for these, and wouldn’t mind the performance trade off in most cases. Especially when running through my tests, and I could easily turn it off for production.


#9

@orestis

Hum, actually I’m a little confused. Your link doesn’t work for me. I get a 404. But the example you gave works in my repl, and I get a warning. That said, given:

(defn foo
  [x]
  (+ x 3))

I get a proper warning when running:

cljs.user=> (+ (foo 1) "a")
            ⬆
WARNING: cljs.core/+, all arguments must be numbers, got [any string] instead. at line 1 
"4a"

But not when running:

cljs.user=> (foo "1")
"13"

I’m not sure why the latter scenario is not being warned on, I feel if the former can, the latter should be able to warn also.

EDIT: Hum, I see my warning says [any string]. So I guess I don’t have the “flow” change you mention. Hopefully when that drops, both examples will warn appropriately.


#10

Seems like the link has changed - to avoid entering another broken link, see the latest news item here: https://github.com/clojure/clojurescript-site/tree/news-next/content/news

Note, this is an unreleased version, so you have to use master to see this new behavior. No idea if it covers your entire use case though.


#11

Unlikely because 1) this isn’t a problem that a significant number of users who are using ClojureScript in production are complaining about 2) the performance impact is bigger than it may seem, persistent data structures rely on fast numerics. There’s also a potential for churn that outsizes the small benefits.

It’s possible we may revisit, but this will remain a low priority thing for the foreseeable future. As usual we have more important fish to fry :slight_smile:


#12

Oh definitly, not even my top item on my wishlist, far from it. It was more of a learning process for me of understanding the difference between Clojure, JavaScript and ClojureScript. This one surprised me quite a bit, and got me curious about the reasons behind it. Though I figured it was most likely performance.

Anyways, thanks for all the explanations and transparency. As much as I love Clojure and ClojureScript for its technical prowess, it’s this transparency and interaction with core members and their friendliness that really set them appart.