Skip to content
A primer for Lisp-style S-expressions for those familiar with C-family languages.
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
LICENSE
README.md

README.md

S-expression Primer

A primer for Lisp-style S-expressions, for those already familiar with C-family languages. S-expressions are at the core of all Lisp-family languages (Lisp, Clojure, Scheme)

They appear frequently in the functional form:

(function ...arguments)
(add 1 2 3)
(toLowerCase "Buggle")

Or the expression form:

(1 2 3 4)
{:a 1, :b 2, :c 5}

Thinking in S-expressions

In C-family languages we think of data structures (Numbers, Strings, Arrays, Objects), operators (+, -, *, ÷, ++, ==), and control structures (if/else, while, for) as separate concerns. In Lisp-family languages we think of all these things as S-expressions.

A Small Example

Let's take this expression using C-style that should feel familiar and convert it to Lisp style code.

"Hello World".substr(1) == "ello World"

Let's first make one modification to help with a conceptual leap. Instead of thinking of == as an operator, let's think of it as a method on the the String class.

"Hello World".substr(1).equals("ello World")

Then we can rewrite this object.method(...args) format code into generic a (function ...args) format.

(== (substr "Hello World" 1) "ello World")

No More Order of Operations

While writing code using S-expressions there is no need to consider Operator precedence, because it simply doesn't need to exist.

1 + 3 + 5 * 7 % 8

Can be rewritten without precedence using Lisp-style expressions.

(+ 1 3 (% (* 5 7) 8)

Mapping and Recursion Instead of Looping

If you've modeled any problem in C-family languages, you've probably come into some sort of looping procedure. This type of control logic doesn't always map directly to a Lisp-family equivalent, but there is usually a function to do a similar manipulation.

This is a fundamental difference. In Lisp languages all code is data, where in C languages data and control structures are separated.

Let's say we have an array of people and we want to create a new array with only doctors included.

const people = ["Mr. Hanky", "Towlie", "Dr. Alphonse Mephesto"]
let doctors = []
for (let i = 0; i < people.length; i++) {
  if (people[i].indexOf('Dr.') !== -1) {
    doctors.push(people[i])
  }
}
// doctors == ["Dr. Alphonse Mephesto"]

Every time we pushed a person into the doctors array above would be considered a side-effect in functional parlance. In Lisp-style languages reduce type functions are used instead.

(def people ["Mr. Hanky", "Towlie", "Dr. Alphonse Mephesto"])
(def doctors
  (filter
    (fn [person] (string/includes? person "Dr."))
    people))
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.