Introduction to Clojure

If you want to follow along...

$ brew install leiningen

c:\> choco install lein (open new cmd after)

$ apt-get install leiningen

Clojure is a Lisp

  • Runs on the JVM (also on .NET)
  • Has a sister: ClojureScript
    • transpiles to JavaScript
    • runs in browsers and Node.js

 

What's a Lisp?

  • "List Processor"
  • 1958 - John McCarthy
  • Processes Lists :)
  • Processes Lists of Lists => trees
  • It has a REPL and it knows how to use it

(+ 1 2)

(+ 1 2 (* 2 3 4))

DEMO TIME!

Follow along:

$ lein repl

Let's Lisp!

Basic Datatypes

123, "String", 1/2, 0.5, \newline

:keyword, :with-namespace/keyword

symbol

true, false, nil

 

Collections

Lists: (1 2 3)

Vectors: [1 2 3]

Sets: #{1 2 3}

Maps: {:a 1, :b 2, "bubu" "lala"}

Special Forms

def - creates top-level binding - (def foo "bar")

defn - defines a function - (defn times-two [x] (* x 2))

fn - lambda - ((fn [x] (* x 2)) 2)

let - local bindings - (let [x1 1, x2 (times-two x1)] (+ x1 x2))

if - then else - (if (> x 0) (dec x) (inc x))

 

DEMO TIME!

Higher-Order Functions

  • Functions that take functions as parameters
  • Functions that return functions

DEMO TIME!

Pure Functions

  • Same return value for same arguments
  • No Side-Effects
  • Easy to test
  • Easy to reason about

The Curios Case Of Or

(or a b c)

 

a b c are all evaluated before or is called!

(or true b c)

Need shortcut!

Need to change the evaluator!

Macro's to the rescue!

DEMO TIME!

State

  • It's evil
  • Avoid
  • If you must: refs, atoms, agents

DEMO TIME!

Why is state evil?

import java.util.HashSet;
import java.util.Set;

public class Person {
    private String name;
    private Set<Person> friends = new HashSet<>();

    Person(String name) { this.name = name; }

    Person(String name, Set<Person> friends) {
        this.name = name;
        this.friends = friends;
    }

    void addFriend(Person friend) {
        friends.add(friend);
    }

    void removeFriend(Person nonGrata) {
        friends.remove(nonGrata);
    }

    Set<Person> getFriends() { return friends; }

    private String getName() { return name; }

    @Override
    public String toString() {
        return getName() + " friends:" + getFriends();
    }
}

Alternative?

  • Use pure functions
  • Push side-effect (i.e. state changes) to the boundary
  • Think: v1 --> f --> v2

More goodies

  • Multimethods
  • Extending Types
  • Java Interop

Beyond Types

Spec

 

class Person { String name; int age; }

 

(s/def ::name string?)

(s/def ::age number?)

(s/def ::person (s/keys :req [::name ::age]))

(s/explain ::person {::name "Picard" ::age 78 })

Wanna try?

lein update-in :dependencies conj '[org.clojure/spec.alpha "0.2.176"]' -- repl

Introduction to Clojure

By Jochen Bedersdorfer

Introduction to Clojure

  • 661