Science and technology

Why we selected the Clojure programming language for Penpot

“Why Clojure?” might be the query we have been requested probably the most at Penpot. We have a imprecise clarification on our FAQ web page, so with this text, I’ll clarify the motivations and spirit behind our determination.

It all began in a PIWEEK. Of course!

During one among our Personal Innovation Weeks (PIWEEK) in 2015, a small group had the thought to create an open supply prototyping device. They began work instantly, and had been in a position to launch a working prototype after per week of laborious work (and plenty of enjoyable). This was the primary static prototype, with no backend.

I used to be not a part of the preliminary group, however there have been many causes to decide on ClojureScript again then. Building a prototype in only a one-week hackathon is not straightforward and ClojureScript actually helped with that, however I feel crucial purpose it was chosen was as a result of it is enjoyable. It provided a practical paradigm (within the group, there was a variety of curiosity in practical languages).

It additionally offered a totally interactive growth surroundings. I’m not referring to post-compilation browser auto-refresh. I imply refreshing the code at runtime with out doing a web page refresh and with out shedding state! Technically, you would be growing a sport, and alter the conduct of the sport if you are nonetheless enjoying it, simply by touching a number of traces in your editor. With ClojureScript (and Clojure) you do not want something fancy. The language constructs are already designed with scorching reload in thoughts from the bottom up.

(Andrey Antukh, CC BY-SA 4.0)

I do know, right this moment (in 2022), it’s also possible to have one thing related with React on plain JavaScript, and possibly with different frameworks that I’m not acquainted with. Then once more, it is also possible that this functionality assist is proscribed and fragile, due to intrinsic limitations within the language, sealed modules, the shortage of a correct REPL.

About REPL

In different dynamic languages, like JavaScript, Python, Groovy (and so forth), REPL options are added as an afterthought. As a end result, they typically have points with scorching reloading. The language patterns are superb in actual code, however they are not appropriate within the REPL (for instance, a const in JavaScript evaluates the identical code twice in a REPL).

These REPLs are often used to check code snippets made for the REPL. In distinction, REPL utilization in Clojure not often includes typing or copying into the REPL straight, and it’s far more frequent to judge small code snippets from the precise supply recordsdata. These are continuously left within the code base as remark blocks, so you need to use the snippets within the REPL once more once you change the code sooner or later.

In the Clojure REPL, you may develop a complete software with none limitations. The Clojure REPL does not behave otherwise from the compiler itself. You’re in a position to do all types of runtime introspection and scorching alternative of particular features in any namespace in an already operating software. In reality, it isn’t unusual to search out backend functions in a manufacturing surroundings exposing REPL on a neighborhood socket to have the ability to examine the runtime and, when essential, patch particular features with out even having to restart the service.

Programming and growth

From prototype to the usable software

After PIWEEK in 2015, Juan de la Cruz (a designer at Penpot, and the unique creator of the mission concept) and I began engaged on the mission in our spare time. We rewrote the complete mission utilizing all the teachings realized from the primary prototype. At the start of 2017, we internally launched what could possibly be referred to as the second practical prototype, this time with a backend. And the factor is, we had been nonetheless utilizing Clojure and ClojureScript!

The preliminary causes had been nonetheless legitimate and related, however the motivation for such an essential time funding reveals different causes. It’s a really lengthy checklist, however I feel crucial options of all had been: stability, backwards compatibility, and syntactic abstraction (within the type of macros).

(Andrey Antukh, CC BY-SA 4.0)

Stability and backwards compatibility

Stability and backwards compatibility are one of the essential objectives of the Clojure language. There’s often not a lot of a rush to incorporate all the fashionable stuff into the language with out having examined its actual usefulness. It’s not unusual to see individuals operating manufacturing on prime of an alpha model of the Clojure compiler, as a result of it is uncommon to have instability points even on alpha releases.

In Clojure or ClojureScript, if a library does not have commits for a while, it is almost definitely superb as is. It wants no additional growth. It works completely, and there is no use in altering one thing that features as meant. Contrarily, within the JavaScript world, once you see a library that hasn’t had commits in a number of months, you are inclined to get the sensation that the library is deserted or unmaintained.

There are quite a few occasions when I’ve downloaded a JavaScript mission that has not been touched in 6 months solely to search out that greater than half of the code is already deprecated and unmaintained. On different events, it doesn’t even compile as a result of some dependencies haven’t revered semantic versioning.

This is why every dependency of Penpot is fastidiously chosen, with continuity, stability, and backwards compatibility in thoughts. Many of them have been developed in-house. We delegate to 3rd get together libraries solely after they have confirmed to have the identical properties, or when the trouble to time ratio of doing it in-house wasn’t price it.

I feel abstract is that we attempt to have the minimal essential exterior dependencies. React might be instance of an enormous exterior dependency. Over time, it has proven that they’ve an actual concern with backwards compatibility. Each main launch incorporates adjustments step by step and with a transparent path for migration, permitting for outdated and new code to coexist.

Syntactic abstractions

Another purpose I like Clojure is its clear syntactic abstractions (macros). It’s a kind of traits that, as a basic rule, generally is a double-edged sword. You should use it fastidiously and never abuse it. But with the complexity of Penpot as a mission, being able to extract sure frequent or verbose constructs has helped us simplify the code. These statements can’t be generalized, and the doable worth they supply have to be seen on a case-by-case foundation. Here are a few essential situations which have made a big distinction for Penpot:

  • When we started constructing Penpot, React solely had parts as a category. But these parts had been modeled as features and interior decorators in a rumext library. When React launched variations with hooks that tremendously enhanced the practical parts, we solely needed to change the implementation of the macro and 90% of Penpot’s parts could possibly be stored unmodified. Subsequently, we have step by step moved from decorators to hooks fully with out the necessity for a laborious migration. This reinforces the identical concept of the earlier paragraphs: stability and backwards compatibility.
  • The second most essential case is the convenience of utilizing native language constructs (vectors and maps) to outline the construction of the digital DOM, as an alternative of utilizing a JSX-like customized DSL. Using these native language constructs would make a macro find yourself producing the corresponding calls to React.createElement at compile time, nonetheless leaving room for added optimizations. Obviously, the truth that the language is expression-oriented makes all of it extra idiomatic.

Here’s a easy instance in JavaScript, based mostly on examples from React documentation:

operate MyComponent({isAuth, choices}) {
    let button;
    if (isAuth) {
        button = <LogoutButton />;
    } else {
        button = <LoginButton />;
    }

    return (
        <div>
          {button}
          <ul>
            {Array(props.whole).fill(1).map((el, i) =>
              <li key={i}>{{merchandise + i}}</li>
            )}
          </ul>
        </div>
    );
}

Here’s the equal in ClojureScript:

(defn my-element [{:keys [auth? options]}]
  [:div
   (if auth?
     [:& logout-button {}]
     [:& login-button {}])
   [:ul
    (for [[i item] (map-listed vector choices)]
      [:li {:key i} item])]])

All these information buildings used to symbolize the digital DOM are transformed into the suitable React.createElement calls at compile time.

The proven fact that Clojure is so data-oriented made utilizing the identical native information buildings of the language to symbolize the digital DOM a pure and logical course of. Clojure is a dialect of LISP, the place the syntax and AST of the language use the identical information buildings and might be processed with the identical mechanisms.

For me, working with React by way of ClojureScript feels extra pure than working with it in JavaScript. All the additional instruments added to React to make use of it comfortably, similar to JSX, immutable information buildings, or instruments to work with information transformations, and state dealing with, are simply part of the ClojureScript language.

Guest Language

Finally, one of many fundamentals of Clojure and ClojureScript ​​is that they had been constructed as Guest Languages. That is, they work on prime of an present platform or runtime. In this case, Clojure is constructed on prime of the JVM and ClojureScript on prime of JavaScript, which signifies that interoperability between the language and the runtime could be very environment friendly. This allowed us to benefit from the complete ecosystem of each Clojure plus the whole lot that is accomplished in Java (the identical is true for ClojureScript and JavaScript).

There are additionally items of code which are simpler to write down after they’re written in crucial languages, like Java or JavaScript. Clojure can coexist with them in the identical code base with none issues.

There’s additionally an ease of sharing code between frontend and backend, although each might be operating in a very completely different runtime (JavaScript and JVM). For Penpot, nearly all crucial logic for managing a file’s information is written in code and executed each within the frontend and within the backend.

Perhaps you would say we now have chosen what some individuals name a “boring” expertise, however with out it really being boring in any respect.

Trade-offs

Obviously, each determination has trade-offs. The selection to make use of Clojure and ClojureScript just isn’t an exception. From a enterprise perspective, the selection of Clojure could possibly be seen as dangerous as a result of it isn’t a mainstream language, it has a comparatively small neighborhood in comparison with Java or JavaScript, and discovering builders is inherently extra sophisticated.

But for my part, the educational curve is way decrease than it may appear at first look. Once you eliminate the it is completely different concern (or as I jokingly name it: concern of the parentheses), you begin to acquire fluency with the language in a short time. There are tons of studying sources, together with books and coaching programs.

The actual impediment I’ve observed is the paradigm shift, relatively than the language itself. With Penpot, the mandatory and inherent complexity of the mission makes the programming language the least of our issues when going through growth: constructing a design platform is not any small feat.


This article initially appeared on the Kaleidos blog and has been republished with permission. 

Most Popular

To Top