Conjuring unicorns
FreePikFreePik
Gradient background

Napkin-Sized Clojure Crash Course

Clarice Bouwer

Software Engineering Team Lead and Director of Cloudsure

Sunday, 24 November 2024 · Estimated 6 minute read

In my previous article, I introduced my journey to learning Clojure. I was taught Clojure basics on a napkin by my friend Len at a kiddies trampoline joint. This article aims to teach you the basics too. So, here’s my "napkin-sized" Clojure crash course for you: In this guide, you'll learn the basics of Clojure, including data types, defining values, syntax, namespaces, functions, and more. The goal is to give you a quick and simple foundation to start coding in Clojure. We will follow with some advanced concepts and how to deepen your understanding of Clojure.

The Basics

The beauty of Clojure lies in its simplicity and elegance. It’s a functional programming language that runs on the Java Virtual Machine (JVM) and emphasizes immutability, simplicity, and expressive code. Clojure is a Lisp dialect, which means it uses a prefix notation syntax and treats code as data. Here’s a quick overview of the basics:

Data Types

  • Numbers: 42, 3.14
  • Strings: "Hello"
  • Keywords: :name (think labels)
  • Lists: (1 2 3) (data and code!)
  • Vectors: [1 2 3] (like arrays)
  • Maps: {:key "value"} (key-value pairs - no guaranteed order)
  • Sets: #{1 2 3} (unique values - no guaranteed order)

Defining Values

  • Defining a symbol with a value: (def x 42) — Like a "named constant."
  • Everything is an expression, no statements.

Syntax

  • Comment: ; (until end of line), #_ (suppresses forms), (comment ...) (multi-line comments)
  • Prefix notation: (operator arg1 arg2)
  • Example: (+ 1 2) yields 3

Namespaces

  • Organize code in namespaces: (ns my-namespace)
  • Use require to include other namespaces.
  • Store your files in a logical directory structure matching the Java package structure. For example, src/my_namespace/core.clj. Note the use of underscores in the namespace name. When you name your namespace, the underscores are converted to hyphens in the file path: (ns my-namespace.core)

Functions

  • Define: (defn add [a b] (+ a b))
  • Call: (add 1 2)3
  • Anonymous: (fn [x] (* x x)) or #(* % %) (for quick/inline execution)

Immutable Data

  • Data doesn’t change. New values are returned.
  • Example: (conj [1 2] 3)[1 2 3] (but [1 2] is still [1 2])

Flow Control

  • if: (if true "yes" "no")"yes"

  • when: (when true (println "yes"))yes

  • cond: Like a multi-branch if

    Copy
    (cond
      (< x 0) "negative"
      (= x 0) "zero"
      :else "positive")

Collections & Sequence Operations

  • map, filter, reduce:

    Copy
    (map inc [1 2 3]) ; => (2 3 4)
    (filter even? [1 2 3 4]) ; => (2 4)
    (reduce + [1 2 3]) ; => 6

It might be a bit bigger than a typical napkin, but notice how concise and simple Clojure actually is? In my experience, things get messy, complicated and frustrating when I don't understand something or bring in some of my old-thinking. That us when I end up writing some pretty bizarre code.

Remember that Clojure is functional. Functions are first-class citizens. Data is immutable. Embrace simplicity. For example, instead of writing complex loops and conditionals, Clojure encourages using higher-order functions like map and reduce to process data more elegantly. It’s the essence of getting started with Clojure!

Going Beyond the Napkin

After grasping the basics—such as data types, functions, and immutability—the next steps to deepen your understanding of Clojure involve practical learning and understanding its idioms, tooling, and ecosystem. Here’s a suggested path forward:

Learn Core Concepts

Learn functional programming patterns like higher-order functions, immutability, recursion, and pure functions, as these concepts are valuable across many programming languages. Dive into Clojure’s powerful macros to understand how they work and why they’re so effective. Additionally, master the sequence abstraction (seq, map, reduce, filter, etc.), which forms the foundation of data manipulation in Clojure. Practice working with Clojure's core data structures: lists, vectors, maps, and sets. Explore persistent data structures to understand how they enable efficient immutability. Additionally, learn destructuring to handle complex data structures with elegance, using let bindings to break data into named values and organize your code logically.

Learn to write more idiomatic Clojure by adopting practices that align with its functional nature. Prefer functions over loops, use threading macros (->, ->>) for cleaner and more readable code, and master powerful tools like reduce, map, filter, and for comprehensions for elegant data transformations.

Understand Clojure's unique approach to concurrency by exploring its core constructs: atoms, refs, and agents, and learning how they differ in managing state. Additionally, dive into core.async to grasp Go-style channels and lightweight concurrency, enabling you to build more responsive and efficient applications.

Practice at the REPL

Clojure shines in the REPL. Test, play, and experiment interactively. Type expressions and see immediate results.

Spend time experimenting in the Clojure REPL with code snippets to get hands-on experience. Dive into the standard library by using (doc some-fn) or (source some-fn) to explore and understand built-in functions. To grasp macros, use macroexpand to see how they transform code. For a closer look, macroexpand-1 shows one level of expansion, while macroexpand reveals the full transformation, making it a powerful tool for debugging and learning.

Use an editor with REPL integration for seamless interactive development. Tools like CIDER (for Emacs), Calva (for VS Code), or Cursive (for IntelliJ) let you send code directly to the REPL from your editor. This approach is a cornerstone of Clojure's workflow, enabling quick feedback and an iterative development process.

Maintain Good Code Hygiene

Copy
(defn y [x] (->> x ((fn [n] (* n n))) ((fn [n] (+ n 10))) ((fn [n] (/ n 2))) ((fn [n] (- n 5)))))

The above is valid Clojure code but you need to be a Demigod to understand it. It is important to maintain good code hygiene by writing clean, readable, and idiomatic Clojure code. Follow the Clojure style guide to ensure consistency in your codebase. Here is a somewhat better refactor:

Copy
(defn nested-calculation
  "Perform a series of calculations on the input value x that calculates the answer to the universe and everything."
  [x]
  (->> x
       ((fn [n] (* n n)))
       ((fn [n] (+ n 10)))
       ((fn [n] (/ n 2)))
       ((fn [n] (- n 5)))))

Use appropriate indentation, meaningful names for functions and variables, and write clear docstrings to explain the purpose of your code. Break down complex expressions into smaller, more manageable parts, and use threading macros (->, ->>) to improve readability and maintainability. Write tests for your code using clojure.test or other testing libraries to ensure correctness and reliability. Use linting tools like clj-kondo to catch common mistakes and enforce best practices in your code.

Solve Real Problems

Familiarize yourself with the tools commonly used in Clojure projects, such as Leiningen or deps.edn. Learn to organize namespaces effectively to separate functionality and maintain clean code. Additionally, explore dependency management and build tools like tools.build to streamline your project workflows.

Use this knowledge to practice your Clojure skills with interactive exercises on websites like 4Clojure or Exercism. You can also write small scripts and explore Babashka to automate tasks such as parsing CSV files, analyzing text, or building a simple to-do list app.

Build small, meaningful projects to apply your Clojure skills. Start with a command-line tool using clojure.core, or create a web app with frameworks like Ring, Compojure, or Reitit. You can also explore working with databases by using next.jdbc or a document store like Datomic.

Study real-world projects by browsing open-source Clojure code on GitHub. Explore popular projects like Metabase or Clj-http to see how developers solve practical problems. Pay attention to how projects are structured, how code is documented, and how functional programming concepts are applied in real scenarios.

Start contributing to small open-source projects or create your own library to sharpen your skills. This experience will push you to write clear documentation, design thorough tests, and carefully consider API design, all of which are crucial for creating robust and user-friendly software.

Explore Clojure Libraries

Get familiar with useful libraries in the Clojure ecosystem, such as:

Explore ClojureScript

Learn ClojureScript to build front-end applications with Clojure's functional programming power. Explore frameworks like Reagent or Re-frame, which are popular choices for creating dynamic and responsive single-page applications.

By tackling these steps, you’ll not only grow more comfortable with Clojure but also start to think in a functional and idiomatic way, which is key to mastering it. The key is hands-on practice—embrace the REPL, write code, break things, and most importantly, have fun!

Conclusion

Clojure’s simplicity, functional approach, and powerful abstractions make it a refreshing experience for those willing to embrace a different way of thinking. By starting with the basics and steadily exploring core concepts, the REPL, and real-world projects, you'll gradually unlock the true potential of Clojure. Remember to keep things simple, embrace immutability, and rely on higher-order functions to write elegant and expressive code. Ultimately, learning Clojure is as much about changing how you think as it is about mastering the language itself—enjoy the journey!

References

Share this article on…


Subscribe to my newsletter

Get notified monthly about any new articles, tutorials, and courses. I promise to keep the emails short and sweet, and never spam you.

By signing up you acknowledge the Privacy Policy. Please keep an eye out in your junk/spam folder for the confirmation email.