The Clojure Workshop: Use functional programming to build data-centric applications with Clojure and ClojureScript
By Joseph Fahey, Thomas Haratyk, Scott McCaughie and
()
About this ebook
Learn how to solve problems using Clojure or ClojureScript and become a confident functional programmer with the help of engaging activities and challenging projects
Key Features- Master the tools and patterns of the Clojure and ClojureScript ecosystems
- Learn the fundamentals of functional programming and immutability
- Apply your skills practically by developing a range of scalable applications
The Clojure Workshop is a step-by-step guide to Clojure and ClojureScript, designed to quickly get you up and running as a confident, knowledgeable developer.
Because of the functional nature of the language, Clojure programming is quite different to what many developers will have experienced. As hosted languages, Clojure and ClojureScript can also be daunting for newcomers because of complexities in the tooling and the challenge of interacting with the host platforms. To help you overcome these barriers, this book adopts a practical approach. Every chapter is centered around building something.
As you progress through the book, you will progressively develop the 'muscle memory' that will make you a productive Clojure programmer, and help you see the world through the concepts of functional programming. You will also gain familiarity with common idioms and patterns, as well as exposure to some of the most widely used libraries.
Unlike many Clojure books, this Workshop will include significant coverage of both Clojure and ClojureScript. This makes it useful no matter your goal or preferred platform, and provides a fresh perspective on the hosted nature of the language.
By the end of this book, you'll have the knowledge, skills and confidence to creatively tackle your own ambitious projects with Clojure and ClojureScript.
What you will learn- Write idiomatic code with Clojure and ClojureScript
- Understand and use common patterns and best practices
- Experiment with code and interact with programs using the REPL
- Learn the fundamentals of functional programming and immutability
- Master concepts including mapping, filtering, reducing and recursion
- Structure and build your code using namespaces and Leiningen
- Write unit tests to validate application behavior
- Simplify your code and improve efficiency with macros
The Clojure Workshop is for anyone who is curious about functional programming and wants to get started learning Clojure or ClojureScript. Prior experience of another programming language, such as Java or JavaScript, is recommended, and will help you grasp the concepts covered in this book more easily.
Related to The Clojure Workshop
Related ebooks
The Go Workshop: Learn to write clean, efficient code and build high-performance applications with Go Rating: 0 out of 5 stars0 ratingsThe Ruby Workshop: Develop powerful applications by writing clean, expressive code with Ruby and Ruby on Rails Rating: 0 out of 5 stars0 ratingsFlask Framework Cookbook Rating: 5 out of 5 stars5/5Learn ClojureScript: Functional programming for the web Rating: 0 out of 5 stars0 ratingsClojure for Java Developers Rating: 0 out of 5 stars0 ratingsClojure Web Development Essentials Rating: 0 out of 5 stars0 ratingsClojure Reactive Programming Rating: 0 out of 5 stars0 ratingsMastering F# Rating: 5 out of 5 stars5/5Elixir Cookbook Rating: 0 out of 5 stars0 ratingsHaskell Design Patterns Rating: 0 out of 5 stars0 ratingsClojure Data Analysis Cookbook - Second Edition Rating: 5 out of 5 stars5/5An Introduction to Functional Programming Through Lambda Calculus Rating: 0 out of 5 stars0 ratingsLearn Rust Programming: Safe Code, Supports Low Level and Embedded Systems Programming with a Strong Ecosystem (English Edition) Rating: 0 out of 5 stars0 ratingsGo Programming Blueprints Rating: 0 out of 5 stars0 ratingsLisp Interpreter in Rust Rating: 1 out of 5 stars1/5Learning Elixir Rating: 0 out of 5 stars0 ratingsLearning Functional Data Structures and Algorithms Rating: 0 out of 5 stars0 ratingsTanmay Teaches Go: The Ideal Language for Backend Developers Rating: 0 out of 5 stars0 ratingsGo Design Patterns Rating: 5 out of 5 stars5/5Kotlin at a Glance: Use of Lambdas and higher-order functions to write more concise, clean, reusable, and simple code Rating: 0 out of 5 stars0 ratingsGolang Mini Reference: A Hitchhiker's Guide to the Modern Programming Languages, #1 Rating: 0 out of 5 stars0 ratingsThe Way to Go: A Thorough Introduction to the Go Programming Language Rating: 3 out of 5 stars3/5Haskell High Performance Programming Rating: 0 out of 5 stars0 ratingsRust In Practice Rating: 0 out of 5 stars0 ratingsGetting Started with LLVM Core Libraries Rating: 0 out of 5 stars0 ratingsBuilding Python Real-Time Applications with Storm Rating: 0 out of 5 stars0 ratingsThe Joy of JavaScript Rating: 0 out of 5 stars0 ratingsDistributed Computing with Python Rating: 0 out of 5 stars0 ratingsModular Programming with Python Rating: 0 out of 5 stars0 ratingsGetting Started with Julia Rating: 0 out of 5 stars0 ratings
Programming For You
SQL QuickStart Guide: The Simplified Beginner's Guide to Managing, Analyzing, and Manipulating Data With SQL Rating: 4 out of 5 stars4/5Linux: Learn in 24 Hours Rating: 5 out of 5 stars5/5Python: Learn Python in 24 Hours Rating: 4 out of 5 stars4/5Coding All-in-One For Dummies Rating: 4 out of 5 stars4/5Learn to Code. Get a Job. The Ultimate Guide to Learning and Getting Hired as a Developer. Rating: 5 out of 5 stars5/5Excel 101: A Beginner's & Intermediate's Guide for Mastering the Quintessence of Microsoft Excel (2010-2019 & 365) in no time! Rating: 0 out of 5 stars0 ratingsPython Programming : How to Code Python Fast In Just 24 Hours With 7 Simple Steps Rating: 4 out of 5 stars4/5Excel : The Ultimate Comprehensive Step-By-Step Guide to the Basics of Excel Programming: 1 Rating: 5 out of 5 stars5/5HTML in 30 Pages Rating: 5 out of 5 stars5/5SQL All-in-One For Dummies Rating: 3 out of 5 stars3/5Python Machine Learning By Example Rating: 4 out of 5 stars4/5Python: For Beginners A Crash Course Guide To Learn Python in 1 Week Rating: 4 out of 5 stars4/5Coding All-in-One For Dummies Rating: 0 out of 5 stars0 ratingsLearn SQL in 24 Hours Rating: 5 out of 5 stars5/5Python Data Structures and Algorithms Rating: 5 out of 5 stars5/5Learn PowerShell in a Month of Lunches, Fourth Edition: Covers Windows, Linux, and macOS Rating: 5 out of 5 stars5/5A Slackers Guide to Coding with Python: Ultimate Beginners Guide to Learning Python Quick Rating: 0 out of 5 stars0 ratingsCoding with JavaScript For Dummies Rating: 0 out of 5 stars0 ratings
Reviews for The Clojure Workshop
0 ratings0 reviews
Book preview
The Clojure Workshop - Joseph Fahey
The Clojure Workshop
Copyright © 2020 Packt Publishing
All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.
Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the authors, nor Packt Publishing, and its dealers and distributors will be held liable for any damages caused or alleged to be caused directly or indirectly by this book.
Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.
Authors: Joseph Fahey, Thomas Haratyk, Scott McCaughie, Yehonathan Sharvit, and Konrad Szydlo
Technical Reviewers: Cavan David, Kirill Erokhin, Punit Naik, David Parker, Prashant Rathod, Erwin Rooijakkers, and John Stevenson
Managing Editor: Rutuja Yerunkar
Acquisitions Editors: Sarah Lawton, Manuraj Nair, Royluis Rodrigues, and Karan Wadekar
Production Editor: Roshan Kawale
Editorial Board: Shubhopriya Banerjee, Bharat Botle, Ewan Buckingham, Megan Carlisle, Mahesh Dhyani, Manasa Kumar, Alex Mazonowicz, Bridget Neale, Dominic Pereira, Shiny Poojary, Abhishek Rane, Brendan Rodrigues, Erol Staveley, Ankita Thakur, Nitesh Thakur, and Jonathan Wray
First Published: January 2020
Production Reference: 3241220
ISBN: 978-1-83882-548-5
Published by Packt Publishing Ltd.
Livery Place, 35 Livery Street
Birmingham B3 2PB, UK
Table of Contents
Preface
1. Hello REPL!
Introduction
REPL Basics
Exercise 1.01: Your First Dance
Exercise 1.02: Getting around in the REPL
Activity 1.01: Performing Basic Operations
Evaluation of Clojure Code
Basic Special Forms
Exercise 1.03: Working with if, do, and when
Bindings
Exercise 1.04: Using def and let
Exercise 1.05: Creating Simple Functions with fn and defn
Activity 1.02: Predicting the Atmospheric Carbon Dioxide Level
Truthiness, nil, and equality
Exercise 1.06: The Truth Is Simple
Equality and Comparisons
Exercise 1.07: Comparing Values
Activity 1.03: The meditate Function v2.0
Summary
2. Data Types and Immutability
Introduction
Simple Data Types
Strings
Numbers
Exercise 2.01: The Obfuscation Machine
Booleans
Symbols
Keywords
Collections
Maps
Exercise 2.02: Using Maps
Sets
Exercise 2.03: Using Sets
Vectors
Exercise 2.04: Using Vectors
Lists
Exercise 2.05: Using Lists
Collection and Sequence Abstractions
Exercise 2.06: Working with Nested Data Structures
Activity 2.01: Creating a Simple In-Memory Database
Summary
3. Functions in Depth
Introduction
Destructuring
Exercise 3.01: Parsing Fly Vector's Data with Sequential Destructuring
Exercise 3.02: Parsing MapJet Data with Associative Destructuring
Advanced Call Signatures
Destructuring Function Parameters
Arity Overloading
Variadic Functions
Exercise 3.03: Multi-arity and Destructuring with Parenthmazes
Higher-Order Programming
First-Class Functions
Partial Functions
Composing Functions
Exercise 3.04: High-Order Functions with Parenthmazes
Multimethods
Exercise 3.05: Using Multimethods
Activity 3.01: Building a Distance and Cost Calculator
Summary
4. Mapping and Filtering
Introduction
map and filter
map
Exercise 4.01: Working with map
filter
Exercise 4.02: Getting Started with filter
Other Members of the filter Family – take-while and drop-while
Exercise 4.03: Partitioning a Sequence with take-while and drop-while
Using map and filter Together
Threading Macros
Using Lazy Sequences
Exercise 4.04: Watching Lazy Evaluation
Exercise 4.05: Creating Our Own Lazy Sequence
Common Idioms and Patterns
Anonymous Functions
Keywords as Functions
Exercise 4.06: Extracting Data from a List of Maps
Sets as Predicates
Filtering on a Keyword with comp and a Set
Exercise 4.07: Using comp and a Set to Filter on a Keyword
Returning a List Longer than the Input with mapcat
Mapping with Multiple Inputs
Exercise 4.08: Identifying Weather Trends
Consuming Extracted Data with apply
Exercise 4.09: Finding the Average Weather Temperature
Activity 4.01: Using map and filter to Report Summary Information
Importing a Dataset from a CSV File
Exercise 4.10: Importing Data from a CSV File
Real-World Laziness
Exercise 4.11: Avoiding Lazy Evaluation Traps with Files
Convenient CSV Parsing
Exercise 4.12: Parsing CSV with semantic-csv
Exercise 4.13: Querying the Data with filter
Exercise 4.14: A Dedicated Query Function
Exercise 4.15: Using filter to Find a Tennis Rivalry
Activity 4.02: Arbitrary Tennis Rivalries
Summary
5. Many to One: Reducing
Introduction
The Basics of reduce
Exercise 5.01: Finding the Day with the Maximum Temperature
Initializing reduce
Partitioning with reduce
Looking Back with reduce
Exercise 5.02: Measuring Elevation Differences on Slopes
Exercise 5.03: Winning and Losing Streaks
Reducing without reduce
zipmap
Exercise 5.04: Creating a Lookup Table with zipmap
Maps to Sequences, and Back Again
group-by
Exercise 5.05: Quick Summary Statistics with group-by
Summarizing Tennis Scores
Exercise 5.06: Complex Accumulation with reduce
Introduction to Elo
Exercise 5.07: Calculating Probabilities for a Single Match
Exercise 5.08: Updating Player Ratings
Activity 5.01: Calculating Elo Ratings for Tennis
Summary
6. Recursion and Looping
Introduction
Clojure's Most Procedural Loop: doseq
Looping Shortcuts
Exercise 6.01: An Endless Stream of Groceries
Recursion at Its Simplest
Exercise 6.02: Partitioning Grocery Bags
When to Use recur
Exercise 6.03: Large-Scale Grocery Partitioning with recur
What about loop?
Exercise 6.04: Groceries with loop
Tail Recursion
Solving Complex Problems with Recursion
Exercise 6.05: Europe by Train
Pathfinding
Exercise 6.06: The Search Function
Exercise 6.07: Calculating the Costs of the Routes
A Brief Introduction to HTML
Activity 6.01: Generating HTML from Clojure Vectors
Summary
7. Recursion II: Lazy Sequences
Introduction
A Simple Lazy Sequence
Consuming a Sequence
Exercise 7.01: Finding Inflection Points
Exercise 7.02: Calculating a Running Average
Lazy Consumption of Data
Lazy Trees
Exercise 7.03: A Tennis History Tree
Exercise 7.04: A Custom take Function
Knowing When to Be Lazy
Exercise 7.05: Formatting the Matches
Activity 7.01: Historical, Player-Centric Elo
Summary
8. Namespaces, Libraries and Leiningen
Introduction
Namespaces
Exercise 8.01: Investigating Namespaces Started by Default in REPL
Exercise 8.02: Navigating Namespaces
Importing Clojure Namespaces Using the refer Function
Exercise 8.03: Using the refer Function to Import a Namespace
Advanced Use of the refer Function
Exercise 8.04: Using the :only Keyword
Exercise 8.05: Using the :exclude Keyword
Exercise 8.06: Using the :rename Keyword
Importing Clojure Functions with require and use
Exercise 8.07: Importing Clojure Functions with require and use
Activity 8.01: Altering the Users List in an Application
When You Want use versus When You Want require
Leiningen—A Build Tool in Clojure
Exercise 8.08: Creating a Leiningen Project
Investigating project.clj
Exercise 8.09: Executing the Application on the Command Line
Exercise 8.10: Executing Application on the Command Line with arguments
Activity 8.02: Summing Up Numbers
Working with External Libraries
Exercise 8.11: Using an External Library in a Leiningen Project
Creating and Executing a jar with Leiningen
Exercise 8.12: Creating a Jar File
Leiningen Profiles
Exercise 8.13: Adding Leiningen Profiles to a Project
User-Wide Profiles
Exercise 8.14: Using User-Wide Profiles
Useful Clojure Libraries
Activity 8.03: Building a Format-Converting Application
Summary
9. Host Platform Interoperability with Java and JavaScript
Introduction
Using Java in Clojure
Exercise 9.01: Importing a Single Java Class in Clojure
Working with Time in Java
Exercise 9.02: Importing Multiple Java Classes in Clojure
Exercise 9.03: Macros That Help Us Use Java in Clojure
Working with Java I/O
Immutability in Clojure
Exercise 9.04: Coffee-Ordering Application – Displaying a Menu
Exercise 9.05: Coffee-Ordering Application – Saving and Loading Orders
Working with Java Data Types
Exercise 9.06: Java Data Types
Activity 9.01: Book-Ordering Application
Using JavaScript in ClojureScript
Exercise 9.07: Working with JavaScript Data Types
Figwheel Template
Reactive Web Programming Using Rum
Exercise 9.08: Investigating Figwheel and Rum
Drag and Drop
Exercise 9.09: JavaScript Interoperability with Drag and Drop
Exceptions and Errors in Clojure
Exercise 9.10: Handling Errors and Exceptions in Clojure
Errors in JavaScript
ClojureScript Leiningen Templates
Exercise 9.11: Handling Errors in ClojureScript
Activity 9.02: Creating a Support Desk
Summary
10. Testing
Introduction
Why Testing Is Important
Functional Testing
Non-Functional Testing
Clojure Unit Testing
Exercise 10.01: Unit Testing with the clojure.test Library
Using the Expectations Testing Library
Exercise 10.02: Testing the Coffee Application with Expectations
Unit Testing with the Midje Library
Exercise 10.03: Testing the Coffee Application with Midje
Property-Based Testing
Exercise 10.04: Using Property-Based Testing in the Coffee-Ordering Application
Activity 10.01: Writing Tests for the Coffee-Ordering Application
Testing in ClojureScript
Exercise 10.05: Setting Up Testing in ClojureScript
Exercise 10.06: Testing ClojureScript Code
Testing ClojureScript Applications with Figwheel
Exercise 10.07: Tests in Figwheel Applications
Exercise 10.08: Testing a ClojureScript Application
Activity 10.02: Support Desk Application with Tests
Summary
11. Macros
Introduction
What is a Macro?
A Very Minimal Macro
Compile Time and Run Time
Runtime Parameters
Syntax Quoting
Exercise 11.01: The and-ors Macro
Exercise 11.02: An Automatic HTML Library
Exercise 11.03: Expanding the HTML Library
Macros in ClojureScript
Macro Hygiene
Avoiding Variable Capture with Automatic Gensyms
Exercise 11.04: Monitoring Functions
When to Use Manual gensyms
Activity 11.01: A Tennis CSV Macro
Interface Design
Implementation
Summary
12. Concurrency
Introduction
Concurrency in General
Automatic Parallelization with pmap
Exercise 12.01: Testing Randomness
Futures
Exercise 12.02: A Crowdsourced Spellchecker
Coordination
Atoms
Concept: Retries
Refs and Software Transactional Memory
Exercise 12.03: Stock Trading
More Cohesion with refs
Exercise 12.04: Keeping up with the Stock Price
Agents
Atoms in ClojureScript
Exercise 12.05: Rock, Scissors, Paper
Watchers
Exercise 12.06: One, Two, Three… Rock!
Activity 12.01: A DOM Whack-a-mole Game
Summary
13. Database Interaction and the Application Layer
Introduction
Connecting to a Database
Exercise 13.01: Establishing a Database Connection
Introduction to Connection Pools
Exercise 13.02: Creating a Connection Pool
Creating Database Schemas
Primary Keys
Foreign Keys
Exercise 13.03: Defining and Applying a Database Schema
Managing Our Data
Inserting Data
Inserting Single Rows
Inserting Multiple Rows
Exercise 13.04: Data Insertion
Querying Data
Exercise 13.05: Querying Our Database
Manipulating Query Return Values
Exercise 13.06: Controlling Results with Custom Functions
Updating and Deleting Data
Exercise 13.07: Updating and Removing Existing Data
Introduction to the Application Layer
Exercise 13.08: Defining the Application Layer
Activity 13.01: Persisting Historic Tennis Results and ELO Calculations
Summary
14. HTTP with Ring
Introduction
HTTP, Web Servers, and REST
Request and Response
Exercise 14.01: Creating a Hello World Web Application
Request Routing
Using Compojure
Exercise 14.02: Introducing Routing with Compojure
Response Formats and Middleware
Exercise 14.03: Response Rendering with Muuntaja
Handling a Request Body
Exercise 14.04: Working with a request Body
Static Files
Exercise 14.05: Serving Static Files
Integrating with an Application Layer
Accessing path and query Parameters in Compojure
Exercise 14.06: Integrating with an Application Layer
Activity 14.01: Exposing Historic Tennis Results and ELO Calculations via REST
Summary
15. The Frontend: A ClojureScript UI
Introduction
Hiccup instead of HTML
Getting Started with Reagent
The Virtual DOM and Component Lifecycle
Exercise 15.01: Creating a Reagent Application
Exercise 15.02: Displaying an Image with Style
Managing Component State
Exercise 15.03: A Button that Modifies Its Text
Components with Children Components
Exercise 15.04: Creating a Grid of Images
Hot Reload
JavaScript Interop
Exercise 15.05: Fetching Data from an HTTP Endpoint
Activity 15.01: Displaying a Grid of Images from the Internet
Activity 15.02: Tennis Players with Ranking
Summary
Appendix
Preface
About
This section briefly introduces the coverage of this book, the technical skills you'll need to get started, and the software requirements required to complete all of the included activities and exercises.
About the Book
The Clojure Workshop is a step-by-step guide to Clojure and ClojureScript, designed to quickly get you up and running as a confident, knowledgeable developer.
Because of the functional nature of the language, Clojure programming is quite different to what many developers will have experienced. As hosted languages, Clojure and ClojureScript can also be daunting for newcomers because of complexities in the tooling and the challenge of interacting with the host platforms. To help you overcome these barriers, this book adopts a practical approach. Every chapter is centered around building something.
As you progress through the book, you will progressively develop the ‘muscle memory' that will make you a productive Clojure programmer, and help you see the world through the concepts of functional programming. You will also gain familiarity with common idioms and patterns, as well as exposure to some of the most widely used libraries.
Unlike many Clojure books, this Workshop will include significant coverage of both Clojure and ClojureScript. This makes it useful no matter your goal or preferred platform, and provides a fresh perspective on the hosted nature of the language.
By the end of this book, you'll have the knowledge, skills and confidence to creatively tackle your own ambitious projects with Clojure and ClojureScript.
Audience
The Clojure Workshop is an ideal tutorial for the Clojure beginner who is just getting started. A basic understanding of JavaScript and Java would be ideal but not necessary. The Clojure Workshop will guide you well throughout the discussion on the interoperability of these technologies.
About the Chapters
Chapter 1, Hello REPL!, gets you typing code immediately. You'll learn the basics of the language, as well as how to get the most out of Clojure's interactive REPL.
Chapter 2, Data Types and Immutability, provides more building blocks, but these are Clojure building blocks that expose you to one of Clojure's key features: immutability.
Chapter 3, Functions in Depth, is a deeper dive into one of the areas that sets Clojure apart: the functional programming paradigm. These are the tools that will power you through the rest of the book.
Chapter 4, Mapping and Filtering, is the first stop on your exploration of Clojure collections. The patterns and techniques here are all about learning to solve problems. The map and filter functions are two of the foremost Clojure workhorses.
Chapter 5, Many to One: Reducing, will really start getting you thinking in new ways. The data-shaping techniques in this chapter complement those in the previous chapter.
Chapter 6, Recursion and Looping, takes your collection techniques to the next level. This chapter will make you think. By the end of the chapter, you'll be ready to handle tricky problems using advanced functional patterns.
Chapter 7, Recursion II: Lazy Sequences, completes the panorama of Clojure collections with a look at a distinctive Clojure feature. If you can write functions to process complex tree structures, you are ready to use Clojure to solve big problems.
Chapter 8, Namespaces, Libraries, and Leiningen, provides a close look at the tools you need for building real-world Clojure and ClojureScript applications. You have the skills to write good Clojure code; now you need to know how to put your application together.
Chapter 9, Host Platform Interoperability with Java and JavaScript, brings you up to speed on a topic that is one of Clojure's great strengths but can also be daunting. As a hosted language, Clojure gives you access to the underlying platform. Knowing how and when to use that power is a key Clojure skill.
Chapter 10, Testing, is another important step in serious, real-world programming. Understanding Clojure and ClojureScript testing stories is a skill every professional programmer needs.
Chapter 11, Macros, will help you understand a distinctive feature of the Lisp family of languages. Macros allow rich abstraction, but underneath the surface, there are a lot of important practical details.
Chapter 12, Concurrency, reveals another unique Clojure strength. This chapter will give you a taste for building multithreaded applications on the Java Virtual Machine or event-driven ClojureScript single-page applications.
Chapter 13, Database Interaction and the Application Layer, shows you how to leverage Clojure's database libraries. Many real applications require databases, so these skills are essential.
Chapter 14, HTTP with Ring, shows you how to set up and run a Clojure-driven web server. The Ring libraries are the most widely used HTTP technology in the Clojure world.
Chapter 15, The Frontend: A ClojureScript UI, helps put together many of the things you've already learned about ClojureScript, the last layer on a Clojure web stack.
Conventions
Code words in text, database table names, folder names, filenames, file extensions, pathnames, dummy URLs, user input, and Twitter handles are shown as follows: Please note that this function is in the clojure.string namespace, which is not referred to by default.
Words that you see on the screen, for example, in menus or dialog boxes, also appear in the text like this: When you click on the Fetch Images button, the images appear with authors' names.
A block of code is set as follows:
(defn remove-large-integers [ints]
(remove #(and (integer? %) (> % 1000)) ints))
In cases where inputting and executing some code gives an immediate output, this is shown as follows:
user=> (sort [3 7 5 1 9])
(1 3 5 7 9)
In the example above, the code entered is (sort [3 7 5 1 9]), and the output is (1 3 5 7 9).
New terms and important words are shown like this: Welcome to the Clojure Read Eval Print Loop (REPL), a command-line interface that we can use to interact with a running Clojure program.
Key parts of code snippets are highlighted as follows:
{:deps {compojure {:mvn/version 1.6.1
}
metosin/muuntaja {:mvn/version 0.6.4
}
ring/ring-core {:mvn/version 1.7.1
}
ring/ring-jetty-adapter {:mvn/version 1.7.1
}}
user=> (require '[muuntaja.middleware :as middleware])
=>nil
Long code snippets are truncated and the corresponding names of the code files on GitHub are placed at the top of the truncated code. The permalinks to the entire code are placed below the code snippet. It should look as follows:
kvitova_matches.clj
1 (def matches
2 [{:winner-name Kvitova P.
,
3 :loser-name Ostapenko J.
,
4 :tournament US Open
,
5 :location New York
,
6 :date 2016-08-29
}
7 {:winner-name Kvitova P.
,
8 :loser-name Buyukakcay C.
,
9 :tournament US Open
,
10 :location New York
,
11 :date 2016-08-31
}
The full code can be found at: https://packt.live/2GcudYj
Before You Begin
Each great journey begins with a humble step. Our upcoming adventure in the land of Clojure is no exception. Before we can do awesome things with data, we need to be prepared with a productive environment. In this section, we will see how to do that.
Installing Java
Before installing Clojure, you need to make sure that you have the Java Developer's Kit (JDK) installed on your computer. For Mac and Linux users, prebuilt binaries are a few keystrokes away. On Mac, for Homebrew users, you can just type:
$ brew install openjdk
On Debian-based Linux distributions, you can check which version is available by typing the following:
$ apt-get search openjdk
Depending on the output, you can then type something like:
$ sudo apt-get install openjdk-11-jdk
Clojure does not require a particularly recent version of the JDK.
For Windows, you can download the OpenJDK installer here: https://packt.live/3aBu1Qg. Once you have the installer, click on it to run, then follow the instructions.
Installing Clojure
Once you have a working JDK on your system, setting up Clojure is easy with the Leiningen tool.
Copy the appropriate version (Windows or Mac/Linux) from the Leiningen home page, here: https://leiningen.org/.
Place Leiningen in a directory that is part of your system's $PATH and make it executable.
On Mac or Linux, this means putting it in a directory such as ~/bin and calling chmod:
$ chmod +x ~/bin/lein
On Windows, to change the $PATH variable, go to Control Panel > User Accounts > User Accounts and click on Change My Environment Variables. In the pane showing the user variables for your personal user account, click on Path and then choose Edit.
Figure 0.1: User accountsFigure 0.1: User accounts
Click on New to add a line, then type in the path to your new bin directory:
Figure 0.2: Adding the path in bin directoryFigure 0.2: Adding the path in bin directory
Now that Leiningen is installed and executable, from the command line, simply type:
$ lein
Leiningen will fetch Clojure and all the libraries it needs to manage Clojure. And now, by simply typing lein repl, you'll have your very first Clojure REPL:
Figure 0.3: REPL startedFigure 0.3: REPL started
Editors and IDEs
While you can certainly do a lot with a REPL running in a console, it's much more convenient to integrate the Clojure REPL into your favorite editor. Plugins exist for just about every editor and environment out there, from Vim to Emacs and from IntelliJ to Electron or Visual Studio Code.
We can't cover all the possible environments here, but we recommend using the coding tools you are already familiar with and adding a Clojure plugin. The best code editor is the one you enjoy using. As long as there is a Clojure plugin for it, you should be up and running in no time.
Installing the Code Bundle
Download the code files from GitHub at https://packt.live/2vbksal. Refer to these code files for the complete code bundle.
If you have any issues or questions about installation, please email us at workshops@packt.com.
The high-quality color images used in book can be found at https://packt.live/2O5EzNX
1. Hello REPL!
Overview
In this chapter, we explain the basics of creating Clojure programs. We start by getting you familiar with the Read Eval Print Loop (REPL), where most of the experimentation happens when writing code. The REPL also allows you to explore code and documentation by yourself, so it is an excellent place to start. After the quick dive in the REPL, we describe in more detail how to read and understand simple Lisp and Clojure code, which syntax can sometimes appear unsettling. We then explore fundamental operators and functions in Clojure, which enable you to write and run simple Clojure programs or scripts.
By the end of this chapter, you will be able to use the REPL and work with functions in Clojure.
Introduction
Have you ever ended up entangled in the spaghetti code
of an object-oriented application? Many experienced programmers would say yes, and at some point in their journey or career would reconsider the foundation of their programs. They might look for a simpler, better alternative to object-oriented programming, and Clojure is an appealing choice. It is a functional, concise, and elegant language of the Lisp family. Its core is small, and its syntax minimal. It shines because of its simplicity, which takes a trained eye to notice and ultimately understand. Employing Clojure's more sophisticated building blocks will allow you to design and build sturdier applications.
Whether you are a seasoned programmer or a novice, hobbyist or professional, C# wizard or Haskell ninja, learning a new programming language is challenging. It is, however, a highly rewarding experience that will make you an overall better programmer. In this book, you will learn by doing and will ramp up your skills quickly.
Clojure is an excellent choice of programming language to learn today. It will allow you to work efficiently using a technology built to last. Clojure can be used to program pretty much anything: from full-blown client-server applications to simple scripts or big data processing jobs. By the end of this book, you will have written a modern web application using Clojure and ClojureScript and will have all the cards in your hand to start writing your own!
REPL Basics
Welcome to the Clojure Read Eval Print Loop (REPL), a command-line interface that we can use to interact with a running Clojure program. REPL, in the sense that it reads the user's input (where the user is you, the programmer), evaluates the input by instantly compiling and executing the code, and prints (that is, displays) the result to the user. The read-eval-print three-step process repeats over and over again (loop) until you exit the program.
The dynamism provided by the REPL allows you to discover and experiment with a tight feedback loop: your code is evaluated instantly, and you can adjust it until you get it right. Many other programming languages provide interactive shells (notably, other dynamic languages such as Ruby or Python), but in Clojure, the REPL plays an exceptional and essential role in the life of the developer. It is often integrated with the code editor and the line between editing, browsing, and executing code blurs toward a malleable development environment similar to Smalltalk. But let's start with the basics.
Throughout these exercises, you may notice some mentions of Java (for example, in the stack trace in the second exercise). This is because Clojure is implemented in Java and runs in the Java Virtual Machine (JVM). Clojure can, therefore, benefit from a mature ecosystem (a battle-tested, widely deployed execution platform and a plethora of libraries) while still being a cutting-edge technology. Clojure is designed to be a hosted language, and another implementation, called ClojureScript, allows you to execute Clojure code on any JavaScript runtime (for example, a web browser or Node.js). This hosted-language implementation choice allows for a smaller community of functional programmers to strive in an industry dominated by Java, .NET Core, and JavaScript technologies. Welcome to the Clojure party, where we're all having our cake and eating it too.
Exercise 1.01: Your First Dance
In this exercise, we will perform some basic operations in the REPL. Let's get started:
Open Terminal and type clj. This will start a Clojure REPL:
$ clj
The output is as follows:
Clojure 1.10.1
user=>
The first line is your version of Clojure, which in this example is 1.10.1. Don't worry if your version is different—the exercises we will go through together should be compatible with any version of Clojure.
The second line displays the namespace we are currently in (user) and prompts for your input. A namespace is a group of things (such as functions) that belong together. Everything you create here will be in the user namespace by default. The user namespace can be considered your playground.
Your REPL is ready to read.
Let's try to evaluate an expression:
user=> Hello REPL!
The output is as follows:
Hello REPL!
In Clojure, literal strings are created with double quotes, . A literal is a notation for representing a fixed value in source code.
Let's see what happens if we type in multiple strings:
user=> Hello
Again
The output is as follows:
Hello
Again
We have just evaluated two expressions sequentially, and each result is printed onto separate lines.
Now, let's try a bit of arithmetic, for example, 1 + 2:
user=> 1 + 2
The output is as follows:
1
#object[clojure.core$_PLUS_ 0xe8df99a clojure.core$_PLUS_@e8df99a
]
2
The output is not exactly what we expected. Clojure evaluated the three components, that is, 1, +, and 2, separately. Evaluating + looks strange because the + symbol is bound to a function.
Note
A function is a unit of code that performs a specific task. We don't need to know more for now except that functions can be called (or invoked) and can take some parameters. A function's argument is a term that's used to design the value of a parameter, but those terms are often used interchangeably.
To add those numbers, we need to call the + function with the arguments 1 and 2.
Call the + function with the arguments 1 and 2 as follows:
user=> (+ 1 2)
The output is as follows:
3
You will soon discover that many basic operations that are usually part of a programming language syntax, such as addition, multiplication, comparison, and so on, are just simple functions in Clojure.
Let's try a few more examples of basic arithmetic. You can even try to pass more than two arguments to the following functions, so adding 1 + 2 + 3 together would look like (+ 1 2 3):
user=> (+ 1 2 3)
6
The other basic arithmetic operators are used in a similar way. Try and type the following expressions:
user=> (- 3 2)
1
user=> (* 3 4 1)
12
user=> (/ 9 3)
3
After typing in the preceding examples, you should try a few more by yourself – the REPL is here to be experimented with.
You should now be familiar enough with the REPL to ask the following question:
user=> (println Would you like to dance?
)
Would you like to dance?
nil
Don't take it personally – nil was the value that was returned by the println function. The text that was printed by the function was merely a side effect of this function.
nil is the Clojure equivalent of null,
or nothing
; that is, the absence of meaningful value. print (without a new line) and println (with a new line) are used to print objects to the standard output, and they return nil once they are done.
Now, we can combine those operations and print the result of a simple addition:
user=> (println (+ 1 2))
3
nil
A value of 3 was printed and the value of nil was returned by this expression.
Notice how we have nested those forms (or expressions). This is how we chain functions in Clojure:
user=> (* 2 (+ 1 2))
6
Exit the REPL by pressing Ctrl + D. The function to exit is System/exit, which takes the exit code as a parameter. Therefore, you can also type the following:
user=> (System/exit 0)
In this exercise, we discovered the REPL and called Clojure functions to print and perform basic arithmetic operations.
Exercise 1.02: Getting around in the REPL
In this exercise, we will introduce a few navigational key bindings and commands to help you use and survive the REPL. Let's get started:
Start by opening the REPL again.
Notice how you can navigate the history of what was typed earlier and in previous sessions by pressing Ctrl + P (or the UP arrow) and Ctrl + N (or the DOWN arrow).
You can also search (case-sensitive) through the history of the commands you have entered: press Ctrl + R and then Hello, which should bring back the HelloAgain expression we typed earlier. If you press Ctrl + R a second time, it will cycle through the matches of the search and bring back the very first command: Hello REPL!. If you press Enter, it will bring the expression back to the current prompt. Press Enter again and it will evaluate it.
Now, evaluate the following expression, which increments (adds 1 to) the number 10:
user=> (inc 10)
11
The returned value is 11, which is indeed 10 + 1.
*1 is a special variable that is bound to the result of the last expression that was evaluated in the REPL. You can evaluate its value by simply typing it like this:
user=> *1
11
Similarly, *2 and *3 are variables bound to the second and third most recent values of that REPL session, respectively.
You can also reuse those special variable values within other expressions. See if you can follow and type this sequence of commands:
user=> (inc 10)
11
user=> *1
11
user=> (inc *1)
12
user=> (inc *1)
13
user=> (inc *2)
13
user=> (inc *1)
14
Notice how the values of *1 and *2 change as new expressions are evaluated. When the REPL is crowded with text, press Ctrl + L to clear the screen.
Another useful variable that's available in the REPL is *e, which contains the result of the last exception. At the moment, it should be nil unless you generated an error earlier. Let's trigger an exception voluntarily by dividing by zero:
user=> (/ 1 0)
Execution error (ArithmeticException) at user/eval71 (REPL:1).
Divide by zero
Evaluating *e should contain details about the exception, including the stack trace:
user=> *e
#error {
:cause Divide by zero
:via
[{:type java.lang.ArithmeticException
:message Divide by zero
:at [clojure.lang.Numbers divide Numbers.java
188]}]
:trace
[[clojure.lang.Numbers divide Numbers.java
188]
[clojure.lang.Numbers divide Numbers.java
3901]
[user$eval1 invokeStatic NO_SOURCE_FILE
1]
[user$eval1 invoke NO_SOURCE_FILE
1]
[clojure.lang.Compiler eval Compiler.java
7177]
[clojure.lang.Compiler eval Compiler.java
7132]
[clojure.core$eval invokeStatic core.clj
3214]
[clojure.core$eval invoke core.clj
3210]
[clojure.main$repl$read_eval_print__9086$fn__9089 invoke main.clj
437]
[clojure.main$repl$read_eval_print__9086 invoke main.clj
437]
[clojure.main$repl$fn__9095 invoke main.clj
458]
[clojure.main$repl invokeStatic main.clj
458]
[clojure.main$repl_opt invokeStatic main.clj
522]
[clojure.main$main invokeStatic main.clj
667]
[clojure.main$main doInvoke main.clj
616]
[clojure.lang.RestFn invoke RestFn.java
397]
[clojure.lang.AFn applyToHelper AFn.java
152]
[clojure.lang.RestFn applyTo RestFn.java
132]
[clojure.lang.Var applyTo Var.java
705]
[clojure.main main main.java
40]]}
Note
Different Clojure implementations may have a slightly different behavior. For example, if you tried to divide by 0 in a ClojureScript REPL, it will not throw an exception and instead return the infinity value
:
cljs.user=> (/ 1 0)
##Inf
This is to stay consistent with the host platform: the literal number 0 is implemented as an integer in Java (and Clojure) but as a floating-point number in JavaScript (and ClojureScript). The IEEE Standard for Floating-Point Arithmetic (IEEE 754) specifies that division by 0 should return +/- infinity.
The doc, find-doc, and apropos functions are essential REPL tools for browsing through documentation. Given that you know the name of the function you want to use, you can read its documentation with doc. Let's see how it works in practice. Start by typing (doc str) to read more about the str function:
user=> (doc str)
-------------------------
clojure.core/str
([] [x] [x & ys])
With no args, returns the empty string. With one arg x, returns
x.toString(). (str nil) returns the empty string. With more than
one arg, returns the concatenation of the str values of the args.
nil
doc prints the fully qualified name of the function (including the namespace) on the first line, the possible sets of parameters (or arities
) on the next line, and finally the description.
This function's fully qualified name is clojure.core/str, which means that it is in the clojure.core namespace. Things defined in clojure.core are available to your current namespace by default, without you explicitly having to require them. This is because they are fundamental components for building your programs, and it would be tedious to have to use their full name every time.
Let's try to use the str function. As the documentation explains, we can pass it multiple arguments:
user=> (str I
will
be
concatenated
) (clojure.core/str This
works
too
)
Iwillbeconcatenated
This works too
Let's inspect the documentation of the doc function:
user=> (doc doc)
-------------------------
clojure.repl/doc
([name])
Macro
Prints documentation for a var or special form given its name,
or for a spec if given a keyword
nil
This function is in the clojure.repl namespace, which is also available by default in your REPL environment.
You can also look at the documentation of a namespace. As its documentation suggests, your final program would typically not use the helpers in the clojure.repl namespace (for instance, doc, find-doc, and apropos):
user=> (doc clojure.repl)
-------------------------
clojure.repl
Utilities meant to be used interactively at the REPL
nil
When you don't know the name of the function, but you have an idea of what the description or name may contain, you can search for it with the find-doc helper. Let's try and search for the modulus operator:
user=> (find-doc modulus
)
nil
No luck, but there's a catch: find-doc is case-sensitive, but the good news is that we can use a regular expression with the i modifier to ignore the case:
user=> (find-doc #(?i)modulus
)
-------------------------
clojure.core/mod
([num div])
Modulus of num and div. Truncates toward negative infinity.
nil
You don't need to know more about regular expressions for now – you don't even have to use them, but it can be useful to ignore the case when searching for a function. You can write them with the #(?i)text
syntax, where text is anything you want to search for.
The function we were looking for was clojure.core/mod.
Let's make sure it works according to its documentation:
user=> (mod 7 3)
1
Use the apropos function to search for functions by name, thereby yielding a more succinct output. Say we were looking for a function that transforms the case of a given string of characters:
user=> (apropos case
)
(clojure.core/case clojure.string/lower-case clojure.string/upper-case)
user=> (clojure.string/upper-case Shout, shout, let it all out
)
SHOUT, SHOUT, LET IT ALL OUT
Please note that this function is in the clojure.string namespace, which is not referred to by default. You will need to use its full name until we learn how to import and refer symbols from other namespaces.
Activity 1.01: Performing Basic Operations
In this activity, we will print messages and perform some basic arithmetic operations in the Clojure REPL.
These steps will help you complete this activity:
Open the REPL.
Print the message I am not afraid of parentheses
to motivate yourself.
Add 1, 2, and 3 and multiply the result by 10 minus 3, which corresponds to the following infix notation: (1 + 2 + 3) * (10 - 3). You should obtain the following result:
42
Print the message Well done!
to congratulate yourself.
Exit the REPL.
Note
The solution for this activity can be found via this link.
Evaluation of Clojure Code
Clojure is a dialect of Lisp, a high-level programming language that was designed by John McCarthy and first appeared in 1958. One of the most distinctive features of Lisp and its derivatives, or dialects,
is the use of data structures to write the source code of programs. The unusual number of parentheses in our Clojure programs is a manifestation of this as parentheses are used to create lists.
Here, we will focus on the building blocks of Clojure programs, that is, forms and expressions, and briefly look at how expressions are evaluated.
Note
The terms expression
and form
are often used interchangeably; however, according to the Clojure documentation, an expression is a form type: Every form not handled specially by a special form or macro is considered by the compiler to be an expression, which is evaluated to yield a value.
We have seen how literals are valid syntax and evaluate to themselves, for example:
user=> Hello
Hello
user=> 1 2 3
1
2
3
We have also learned how to invoke functions by using parentheses:
user=> (+ 1 2 3)
6
It is worth noting at this point that comments can be written with ;
at the beginning of a line. Any line starting with ;
will not be evaluated:
user=> ; This is a comment
user=> ; This line is not evaluated
Functions are invoked according to the following structure:
; (operator operand-1 operand-2 operand-3 …)
; for example:
user=> (* 2 3 4)
24
Take note of the following from the preceding example:
The list, denoted by opening and closing parenthesis, (), is evaluated to a function call (or invocation).
When evaluated, the * symbol resolves to the function that implements the multiplication.
2, 3, and 4 are evaluated to themselves and passed as arguments to the function.
Consider the expression you wrote in Activity 1.01, Performing Basic Operations: (* (+ 1 2 3) (- 10 3)). It can also help to visualize the expression as a tree:
Figure 1.1: Tree representation of the expression, (* (+ 1 2 3) (- 10 3))Figure 1.1: Tree representation of the expression, (* (+ 1 2 3) (- 10 3))
Evaluating this expression consists of reducing the tree, starting with the offshoots (the innermost lists): (* (+ 1 2 3) (- 10 3)) becomes (* 6 7), which becomes 42.
The term s-expression (or symbolic expression) is often used to designate those types of expressions. You may come across it again, so it is good to know that an s-expression is a data notation for writing data structures and code with lists, as we demonstrated previously.
So far, we have only used literal scalar types as operands to our operators, which hold one value, such as numbers, strings, Booleans, and so on. We've only used lists to invoke functions and not to represent data. Let's try to create a list that represents data but not code
:
user=> (1 2 3)
Execution error (ClassCastException) at user/eval255 (REPL:1).
java.lang.Long cannot be cast to clojure.lang.IFn
An exception was thrown because the first item of the list (the operator) was not a function.
There is a special syntax to prevent the list from being considered as the invocation of a function: the quote. Creating a literal list is done by adding a quotation ', in front of it, so let's try again:
user=> '(1 2 3)
(1 2 3)
user=> '(a
b
c
d
)
(a
b
c
d
)
Great! By preventing the evaluation of the form, we can now write a literal representation of lists.
This concept will help us get ready for what we are going to cover next. It is, however, fascinating to notice at this point that Clojure code is made up of data structures, and our programs can generate those same data structures. Code is data
is a famous saying in the Lisp world, and a powerful concept that allows your program to generate code (known as meta-programming). If you are new to this concept, it is worth pausing for a minute to think and admire the sheer beauty of it. We will explain meta-programming techniques in detail later when explaining macros in Chapter 11, Macros.
Basic Special Forms
So far, we have been writing code that complies with the simplest rules of evaluating Clojure code, but there are some behaviors that cannot simply be encoded with normal functions. For example, arguments that have been passed to a function will always be resolved or evaluated, but what if we do not want to evaluate all the operands of an operator? That is when special forms come into play. They can have different evaluation rules for functions when the source code is read by Clojure. For example, the special form if, may not evaluate one of its arguments, depending on the result of the first argument.
There are a few other special forms that we will go through in this section:
when, which can be used when we are only interested in the case of a condition being truthy (a value is truthy when considered true in the context of a Boolean expression).
do, which can be used to execute a series of expressions and return the value of the last expression.
def and let, which are special forms that are used to create global and local bindings.
fn and defn, which are special forms that are used to create functions.
All these special forms have special evaluation rules, all of which we will discover by working through the following three exercises.
Exercise 1.03: Working with if, do, and when
In this exercise, we will evaluate expressions using the if, do, and when forms. Let's get started:
Start your REPL and type in the following expression:
user=> (if true Yes
No
)
Yes
The special form if, evaluates its first argument. If its value is truthful, it will evaluate argument 2, otherwise (else), it will evaluate argument 3. It will never evaluate both arguments 2 and 3.
We can nest expressions and start doing more interesting things:
user=> (if false (+ 3 4) (rand))
0.4833142431072903
In this case, the computation of (+ 3 4) will not be executed, and only a random number (between 0 and 1) will be returned by the rand function.
But what if we wanted to do more than one thing in our branch of the condition? We could wrap our operation with do. Let's see how do works:
user=> (doc do)
-------------------------
do
(do exprs*)
Special Form
Evaluates the expressions in order and returns the value of
the last. If no expressions are supplied, returns nil.
Please see http://clojure.org/special_forms#do
Evaluates the expressions in order and returns the value of
the last. If no expressions are supplied, returns nil.
nil
To use the special form, do type the following expression:
user=> (do (* 3 4) (/ 8 4) (+ 1 1))
2
All the expressions before the final (+ 1 1) expression were evaluated, but only the value of the last one is returned. This does not look very useful with expressions that don't alter the state of the world, and so it would typically be used for side effects such as logging or any other kind of I/O (filesystem access, database query, network request, and so on).
You don't have to take my word for it, so let's experiment with the side effect of printing to the Terminal:
user=> (do (println A proof that this is executed
) (println And this too
))
A proof that this is executed
And this too
nil
Finally, we can combine the use of if and do to execute multiple operations in a conditional branching:
user=> (if true (do (println Calculating a random number...
) (rand)) (+ 1 2))
Calculating a random number...
0.8340057877906916
Technically, you could also omit the third argument. Bring back the previous expression in the REPL and remove the last expression, that is, (+ 1 2):
user=> (if true (do (println Calculating a random number...
) (rand)))
Calculating a random number...
0.5451384920081613
user=> (if false (println Not going to happen
))
nil
We have a better construct available for this case: the when operator. Instead of combining if and do, when you are only interested in doing work in one branch of the conditional execution, use when.
Type the following expression to use when instead of a combination of if and do:
user=> (when true (println First argument
) (println Second argument
) And the last is returned
)
First argument
Second argument
And the last is returned
By completing this exercise, we have demonstrated the usage of the special forms known as if, do, and when. We can now write expressions that contain multiple statements, as well as conditional expressions.
Bindings
In Clojure, we use the term bindings rather than variables and assignments because we tend to bind a value to a symbol only once. Under the hood, Clojure creates variables and so you may encounter this term, but it would be preferable if you don't think of them as classic variables or values that can change. We won't use the term variable anymore in this chapter as it can be confusing. You can use def to define global bindings and let for local bindings.
Exercise 1.04: Using def and let
In this exercise, we will demonstrate the usage of the def and let keywords, which are used to create bindings. Let's get started:
The special form def allows you to bind a value to a symbol. In the REPL, type the following expression to bind the value 10 to the x symbol:
user=> (def x 10)
#'user/x
Note
When the REPL returns #'user/x, it is returning a reference to the var you have just created. The user part indicates the namespace where the var is defined. The #' prefix is a way of quoting the var so that we see the symbol and not the value of the symbol.
Evaluate the expression, x, which will resolve the x symbol to its value:
user=> x
10
Technically, you can change the binding, which is fine when experimenting in the REPL:
user=> (def x 20)
#'user/x
user=> x
20
It is, however, not recommended in your programs because it can make it hard to read and complicate its maintenance. For now, it would be better if you just consider such a binding as a constant.
You can use the x symbol within another expression:
user=> (inc x)
21
user=> x
20
Wherever def is invoked, it will bind the value to the symbol in the current namespace. We could try to define a local binding in a do block and see what happens:
user=> x
20
user=> (do (def x 42))
#'user/x
user=> x
42
The bindings that are created by def have an indefinite scope (or dynamic scope) and can be considered as global.
They are automatically namespaced, which is a useful trait to avoid clashing with existing names.
If we want to have a binding available only to a local scope or lexical scope, we can use the special form let. Type the following expression to create a lexical binding of the y symbol:
user=> (let [y 3] (println y) (* 10 y))
3
30
let takes a vector
as a parameter to create the local bindings, and then a series of expressions that will be evaluated like they are in a do block.
Note
A vector is similar to a list, in the sense that they both are a sequential collection of values. Their underlying data structure is different, and we will shed light on this in Chapter 2, Data Types and Immutability. For now, you just need to know that vectors can be created with square brackets, for example, [1 2 3 4].
Evaluate the y symbol:
user=> y
Syntax error compiling at (REPL:0:0).
Unable to resolve symbol: y in this context
An error is thrown, that is, Unable to resolve symbol: y in this context, because we are now outside of the let block.
Type the following expression to create a lexical binding of x to the value 3, and see how it affects the indefinite (global) binding of x that we created in step 4:
user=> (let [x 3] (println x))
3
nil
user=> x
42
Printing x yields the value 3, which means that the global
x symbol was temporarily overridden or shadowed
by the lexical context in which println was invoked.
You can create multiple local bindings at once with let by passing an even number of items in the vector. Type the following expression to bind x to 10 and y to 20:
user=> (let [x 10 y 20] (str x is
x and y is
y))
x is 10 and y is 20
Combine the concepts of this section and write the following expressions:
user=> (def message Let's add them all!
)
#'user/message
user=> (let [x (* 10 3)
y 20
z 100]
(println message)
(+ x y z))
Let's add them all!
150
The expression spans over multiple lines to improve readability.
Exercise 1.05: Creating Simple Functions with fn and defn
The special form that's used to define functions is fn. Let's jump right into it by creating our first function:
Type the following expression in your REPL:
user=> (fn [])
#object[user$eval196$fn__197 0x3f0846c6 user$eval196$fn__197@3f0846c6
]
We have just created the simplest anonymous function, which takes no parameters and does nothing, and we returned an object, which is our function with no name.
Create a function that takes a parameter named x and return its square value (multiply it by itself):
user=> (fn [x] (* x x))
#object[user$eval227$fn__228 0x68b6f0d6 user$eval227$fn__228@68b6f0d6
]
Remember that, in Clojure, the first item of an expression will be invoked, so we can call our anonymous function by wrapping it with parentheses and providing an argument as the second item of the expression:
user=> ((fn [x] (* x x)) 2)
4
Now this is great, but not very convenient. If we wanted our function to be reusable or testable, it would be better for it to have a name. We can create a symbol in the namespace and bind it to the function.
Use def to bind the function returned by the special form, fn, to the square symbol:
user=> (def square (fn [x] (* x x)))
#'user/square
Invoke your newly created function to make sure that it works:
user=> (square 2)
4
user=> (square *1)
16
user=> (square *1)
256
This pattern of combining def and fn is so common that a built-in macro was born out of necessity: defn. Recreate the square function with defn instead of def and fn:
user=> (defn square [x] (* x x))
#'user/square
user=> (square 10)
100
Did you notice that the x argument was passed in a vector? We have already learned that vectors are collections, and so we can add more than one symbol to the argument's vector. The values that are passed when calling the function will be bound to the symbols provided in the vector during the function's definition.
Functions can take multiple arguments, and their bodies can be composed of multiple expressions (such as an implicit do block). Create a function with the name meditate that takes two arguments: a string, s, and a Boolean, calm. The function will print an introductory message and return a transformation of s based on calm:
user=>
(defn meditate [s calm]
(println Clojure Meditate v1.0
)
(if calm
(clojure.string/capitalize s)
(str (clojure.string/upper-case s) !
)))
Note
Editing multiline expressions in the REPL can be cumbersome. As we start creating lengthier functions and expressions that span multiple lines, it would be preferable to have a window of your favorite editor open next to your REPL window. Keep those windows side by side, edit the code in your editor, copy it to your clipboard, and paste it into your REPL.
The function body contains two main expressions, the first of which is a side effect with println and the second of which is the if block, which will determine the return value. If calm is true, it will politely return the string capitalized (with the first character converted into uppercase), otherwise it will shout and return the string with all its characters to uppercase, ending with an exclamation mark.
Let's try and make sure that our function works as intended:
user=> (meditate in calmness lies true pleasure
true)
Clojure Meditate v1.0
In calmness lies true pleasure
user=> (meditate in calmness lies true pleasure
false)
Clojure Meditate v1.0
IN CALMNESS LIES TRUE PLEASURE!
If we call the function with only the first parameter, it will throw an exception. This is because the parameters that we have defined are required:
user=> (meditate in calmness lies true pleasure
)
Execution error (ArityException) at user/eval365 (REPL:1).
Wrong number of args (1) passed to: user/meditate
One last thing to end our initial tour of these functions is the doc-string parameter. When provided to defn, it will allow you to add a description of your function.
Add documentation to your square function by adding a doc-string just before the function arguments:
user=>
(defn square
Returns the product of the number `x` with itself
[x]
(* x x))
#'user/square
The doc-string is not only useful when browsing a project's source code – it also makes it available to the doc function.
Look up the documentation of your square function with doc:
user=> (doc square)
-------------------------
user/square
([x])
Returns the product of the number `x` with itself
nil
It is important to remember that the doc-string needs to come before the function arguments. If it comes after, the string will be evaluated sequentially as part of the function body and won't throw an error. It is valid syntax, but it will not be available in the doc helper and other development tools.
It is good practice to document the arguments with backticks, `, like we did with `x`, so that development tools (such as the IDE) can recognize them.
We will take a deeper dive into functions in Chapter 3, Functions in Depth, but these few basic principles will get you a long way in terms of writing functions.
Activity 1.02: Predicting the Atmospheric Carbon Dioxide Level
Carbon dioxide (CO2) is an important heat-trapping (greenhouse) gas, currently rising and threatening life as we know it on our planet. We would like to predict future levels of CO2 in the atmosphere based on historical data provided by National Oceanic and Atmospheric Administration (NOAA):
Figure 1.2: CO2 parts per million (ppm) over the yearsFigure 1.2: CO2 parts per million (ppm) over the years
Note
The preceding chart was taken from https://packt.live/35kUI7L and the data was taken from NOAA.
We will use the year 2006 as a starting point with a CO2 level of 382 ppm and calculate the estimate using a simplified (and optimistic) linear function, as follows: Estimate = 382 + ((Year - 2006) * 2).
Create a function called co2-estimate that takes one integer parameter called year and returns the estimated level of CO2 ppm for that year.
These steps will help you complete this activity:
Open your favorite editor and a REPL window next to it.
In your editor, define two constants, base-co2 and base-year, with the values 382 and 2006, respectively.
In