What is Clojure and how it is different from ClojureScript?

First, we'll briefly cover what Clojure is and how it works. After that, we'll see what ClojureScript is and how it's different.

Note for readers

This article was written for those who are absolutely new to Clojure and ClojureScript and have a problem distinguishing what hides behind these two terms. It is not about nitty-gritty details. This article was originally posted at kozieiev.com.

What is Clojure?

Clojure is a programming language that was created by Rich Hickey. His idea was to make a functional language that has strong concurrency support, and that works on Java Virtual Machine (JVM). Being a JVM-hosted language, Clojure has access to every platform that Java works on, with the ability to utilize a massive amount of preexisting code.

What is JVM?

A basic understanding of JVM is helpful for understanding Clojure, so let's take a look at what the Java compilation process looks like:

00-java-compilation.png

In languages like C++, the compiler takes source code and turns it into a platform-dependent machine code that can be run by OS. Java is different, however. Its compiler takes *.java source code and compiles it into *.class files with bytecode. Unlike machine code, bytecode is a platform-independent format. So, Java compilers on different OSes will produce the same bytecode from the same source code.

Then, JVM comes into play. Its class loader brings .class files to the RAM, verifies for security breaches, and finally, the internal JIT compiler converts bytecode to native machine code that works on the specific target platform where you run it.

So JVM is a set of tools that makes running bytecode on a target platform possible.

What does "Clojure is a JVM-hosted Language" mean?

The two-step compilation shown in the previous section has two perks. First, the Java compiler doesn't have to know about machine code for different platforms; JVM handles that. Second, you are not restricted to using the Java compiler to get bytecode. You can write your own compiler that produces bytecode, and JVM can still run it.

And this is exactly the approach Clojure takes. Clojure doesn't have its own tools for generating machine code and running it on the target platform. Instead, it creates bytecode from Clojure sources and leaves the rest of the work for JVM. The diagram of the compilation process looks quite similar to the one for Java:

01-clojure-compilation.png

Clojure compilation example

Let's run some example code (or just skip this section if you'd like).

If you don't have Clojure installed, please take a look at the Getting Started guide on the official site for instructions.

Then, create the following folder structure for a simplistic Clojure app:

project-dir        (project directory; choose any name you want)
├─ src             (in this folder, the compiler will look for sources)
│  └─ main
│     └─ test.clj  (source file with the main function)
├─ classes         (the empty folder, where generated classes will be stored)
└─ deps.edn        (configuration file)

Please make sure you've created an empty classes folder!

In test.clj:, we have a helloworld code:

(ns test.main
  (:gen-class))

(defn -main []
  (println "Hello world!"))

deps.edn: contains configurations for the compiler. Here, we specify the sources folder:

{:paths ["src"]}

Now, let's run the compiler from project-dir:

clj -M -m test.main

You will receive an output.

Hello world!

By default, you won't see the intermediate *.class files generated. But if you do want to take a look at them, run the clj command in the project-dir, and after it shows you a prompt, invoke compilation function:

(compile `test.main)

Now, under the project-dir/classes/test folder, you will find the generated *.class files**. If you forgot to create an empty classes folder at the start, you'll encounter an error.

What is ClojureScript?

When we refer to the term "Clojure," we usually mean both the programming language itself, as well as all its tooling. But let's look at these separately.

There is a "Clojure programming language," meaning rules and syntax we use to describe how a program should work. Then, there is a "Clojure compiler," meaning a toolset that takes Clojure language as an input and turns it into Java bytecode.

When we make this distinction clear, it's easy to understand what ClojureScript is. It is a compiler for the Clojure programming language that turns Clojure code into JavaScript code. (not into Java bytecode as Clojure compiler does).

Just as Clojure allows you to utilize the power of functional programming in tasks and environments where Java is applicable, ClojureScript does the same for environments where JavaScript is applicable.

There is some difference in language support between Clojure and ClojureScript compilers. You can read more details about that on the official site

ClojureScript compilation process

Let's look at the ClojureScript compilation diagram. It's different from before, because JVM is missing.

02-clojurescript-compilation.png

You will still have JVM running, because the ClojureScript compiler is written in Clojure, and as a Clojure app, it's run on JVM; But it is not essential here, because we can imagine an alternate world where the ClojureScript compiler is written in some other language.

Google Closure Tools

You may be wondering what is Google Closure Tools on a previous diagram. Please note, the spelling is Closure; not Clojure! It is a set of tools for JavaScript from Google.

Google Closure includes a lot of useful JavaScript libraries. They are pre-packaged with ClojureScript, and can be used straight away.

Also, there is a Google Closure Compiler that compiles JavaScript into optimized and minified JavaScript. ClojureScript uses Closure compiler when the optimizations setting is turned on.

ClojureScript compilation example

Let's run some ClojureScript code. (As before, feel free to skip this section).

Here is the folder structure for our ClojureScript app:

project-dir        (project directory, choose any name you want)
├─ src             (in this folder compiler will look for sources)
│  └─test            
│    └─ main.cljs  (source file, notice that extension is *.cljs, not *.clj)
└─ deps.edn        (configuration file)

The main file is test.cljs:

(ns test.main
(:require goog.string.format))

(println (goog.string.format "Hello %s" "world"!))

The goog.string.format function in our test.clj is absolutely not necessary for such a primitive app. I added it as an illustration of how easily you can use code from the Google Closure library in ClojureScript.

deps.edn contains a setting that tells that our project needs a ClojureScript package:

{:deps {org.clojure/clojurescript {:mvn/version "1.10.758"}}}

Now, lets run the compiler from the project directory:

clj -M -m cljs.main --target node --output-to ./out/main.js -c test.main

As a result of the ClojureScript compilation, you will get a generated JavaScript file, out/main.js. It can be distributed and used in a JavaScript environment. For example, with node:

node out/main.js

You should see the output,

Hello World!

Note on compiler arguments

You may be confused by the fact that the command-line arguments for running Clojure and ClojureScript compilers are quite similar, and invoke the same clj command. Take a look:

clj -M -m test.main
clj -M -m cljs.main --target node --output-to ./out/main.js -c test.main

This is because the ClojureScript compiler itself is written in Clojure!

So, in the first example, we ran our code, test.main namespace, as a Clojure app. Similarly, in the second example, we ran cljs.main namespace, which is a ClojureScript compiler app, and passed additional arguments to it as instructions on how to compile ClojureScript.

Conclusion

  • Clojure is a functional programming language.
  • The Clojure compiler turns Clojure sources into bytecode and runs it on the JVM.
  • ClojureScript is another compiler for Clojure language that takes source code and compiles it into JavaScript language.

bmc-button.png