Clojure for Java Developers

Jochen Bedersdorfer (@beders)

What is Clojure?

  • Lisp-based programming language
  • Runs on JVM, Node.js and the browser
  • Enforces functional programming with immutable data-structures and safe concurrency
  • Interactive development with Read-Eval-Print-Loop (REPL)
  • Easy interop with Java/JavaScript

Go down for more details ➠

Key insights

  • Changes how you think about data
  • Changes how you think about functions
  • Awareness of side-effects
  • Value values - avoid state
  • Understand time as transition between values
  • Prefer bottom-up coding to top-down coding
  • Avoid complexity

OOP

FP

vs.

Stateful objects: Objects send messages between each other

Functions transform data

OOP

  • Any other object referencing this Starship object sees change in torpedo count
  • The Starship object has no control over this - other than hiding the state completely
  • Concurrency behavior is undefined in most OOP languages

FP

  • T1,T2 are immutable values (like the number 1 is a value)
  • Anything referencing T1 will always see the same value
  • A "change" in T1 instead creates T2
  • T1 and T2 are safe to share between threads
  • This sounds expensive in theory, in practice structural sharing makes it cheap

Hello Java

Hello Clojure

class Hello {
    public static void main(String... args) {
        System.out.prinln("Hello Java");
    }
}


(println "Hello Clojure")

$ javac Hello.java

$ java Hello

 

$ clj hello.clj

Basics

Java

Clojure

// Strings
"Hello World"

// chars
'H' '\n'

// byte, short, int, long
10, 100, 1000, 1000L

// BigInteger
new BigInteger("42000000000000000000");

// float, doubles
0.25f, 0.25d


// closest equivalent to simple keywords:
static final String NOT_A_KEYWORD 
  = "not_a_keyword";

// identifiers like class,field method names
HelloWorld

// other terminals:
true
false
null
;; Strings (on JVM: java.lang.String)
"Hello World"

;; chars
\H \newline

;; Numbers, regular int,longs
1000
;; big integer
42000000000000000000

;; Ratios!
(+ 1/2 1/4) 
=> 3/4

;; floating point
0.5

;; Keywords
:i-am-a-keyword

;; Symbols
'function-names-for-example

;; special symbols
true
false
nil

Basic DATA Types

Method Call

Function Call

Math.min(1,2,3)

// Methods attached to classes, objects
// arguments are comma separated

"Java".toUpperCase()
(min 1 2 3)

;; () denotes lists
;; first list element is function to call
;; rest is arguments
;; Everything is an expression (s-exp),
;; and returns a value
;; now you can read clojure code

(upper-case "Clojure")

;; you can also use Java methods through interop
(.toUpperCase "Clojure")

Nested Calls

Math.min(1,2, Math.max(3,4))


(min 1 2 (max 3 4))

Nested Expression

Compound Expressions

3 + 4 * 2 % 6


// mind the operator precedence rules!
// Java has 15 
// JavaScript has 20
// == for boolean expressions


1 + 2 + 3 + 4 == 4 * 5 / 2
=> true
(mod (+ 3 (* 4 2)) 6)

;; no operator precedence
;; since no operators
;; +/-* etc. are n-ary functions
;; = is boolean predicate to compare things



(= (+ 1 2 3 4) (/ (* 4 5) 2))
=> true

;; Also:
;; these are lists i.e. data!
;; code in Clojure is just data

Just Nested Expression

Statements

// assignment statements, sometimes expressions
String a = "Vogon", b = "Vorgon";

int calc() {
  // local variables
  int x = 10, y = 2*x; 
  return x * y;
}

// flow control statements
for (int i = 0; 
     i < Math.min(a.length, b.length); 
     i++) {
  if (a[i] != b[i]) {
    break;
   }
}

// special loop/branching syntax
// no return value
(def a "Vogon") ; namespace-level Var
(def b "Vorgon")

;; local bindings
(let [x 10
      y (* 2 x)]
  (* x y))
=> 200

;; flow control stmts are also expressions
(for [i (range 0 (min (count a) (count b)))
     :while (= (nth a i) (nth b i))]
     (nth a i))

;; but: for is actually list comprehension
;; iterative loops are rare and often unnecessary
;; there's a return value: minimum match
=> (\V \o)

Still: Just Expressions

Methods/Lambda-Expressions

class Rectangle {
  static int area(int width, int height) {
    return width * height;
  }
}

// lambda

IntBinaryOperator op =
   (width,height) -> width*height;

// syntatic sugar:
op(1,2)  // ooopps
|  Error:
|  cannot find symbol
|    symbol:   method op(int,int)

op.applyAsInt(1,2)
=> 2

// still instance of a class
// with a method
op.getClass().getDeclaredMethods();
=> public int $Lambda$15/1073502961
      .applyAsInt(int,int)
(defn area [width height]
    (* width height))

;; ⇧ two-argument function
;; [] used for binding arguments
;; return value:
;; last expression in function

;; as lambda
(fn [width height]
  (* width height))

;; short-hand
#(* %1 %2)

;; can be invoked like a named function
(#(* %1 %2) 2 3)
=> 6

;; use apply to call functions with arguments in a collection
(apply + [1 2 3])
=> 6
;; vs (+ 1 2 3)

Functions/Lambdas

Function Definition

Function References

List<String> names = List.of("Jim", "Spock",
 "Bones");

names.stream().map(String::toUpperCase)
  .collect(Collectors.toList());


// String::toUpperCase is compiler magic
// not a real function handle


class Counter2 {
  static IntSupplier counter() {
    int count = 0;
    return () ->  count++;
  }
}
// Error:(48, 19) java: local variables 
// referenced from a lambda expression ...
// -> no closures over local variables


public class Singleton {
   public static Singleton INSTANCE = 
      new Singleton();
   public Singleton getSingleton() {
      return INSTANCE;
   }
}

// no general purpose partial application
// or function composition
(def names ["Jim" "Spock" "Bones"])

(map str/upper-case names)
=>
("JIM" "SPOCK" "BONES")

;; str is alias for clojure.string namespace
;; str/upper-case is fully qualified
;; name of the function

(defn make-counter [start]
  (let [counter (atom start)]
    (fn [] (swap! counter inc))))

;; counter is an atom
;; swap! applies inc to counter
;; and stores result in counter
;; atoms are thread-safe

(def cnt (make-counter 0))

(cnt)
=> 1
(cnt)
=> 2

;; 'singleton'
(def singleton (memoize 
  (fn [] :singleton)))

Functions as values

Higher-Order Functions

;; partial takes a function and one or more arguments 
;; and returns a function with those arguments fixed
;; in this case the multiplication function * which can take n-arguments
(def times-two (partial * 2))

(times-two 3)
=> 6

;; comp applies functions from right-to-left
;; mathematically: f(g(x)) = f o g 
(def trim-and-up (comp str/upper-case str/trim))

(trim-and-up " don't panic! ")
=> "DON'T PANIC!"

;; juxt returns a function which creates a sequence by applying each function to its arguments
((juxt first rest) [1 2 3])
=> [1 (2 3)]

;; create pairs of a string and its length
(map (juxt identity count) ["length" "of" "each" "string"])
=> (["length" 6] ["of" 2] ["each" 4] ["string" 6])

;; sort it by length: second takes the second element of the list above
(sort-by second (map (juxt identity count) ["length" "of" "each" "string"]))
=> (["of" 2] ["each" 4] ["length" 6] ["string" 6])

Partials, compositions, juxtaposition, sort-by ...

Higher-Order Functions 2

;; macros extends the expressiveness of the language
;; examples:

;; logical test with short circuit
;; i.e. doesn't eval c if b is false!
(and a b c)

;; use macroexpand to see code produced
(macroexpand '(and a b c))
=> (let* [and__5236__auto__ a] (if and__5236__auto__ (clojure.core/and b c) and__5236__auto__))


;; re-writing nested expressions using threading macros
(def person {:name "Khan Noonien Singh"})
(str (str/upper-case (first (str/split (:name person) #" "))) "!!!!")
;; too much nesting gets tricky to read

;; -> macro flattens this
(-> person
    :name
    (str/split #" ")
    first
    str/upper-case
    (str "!!!!"))
=> "KHAN!!!!"

Extend syntax of Clojure

 

Macros

(macroexpand '.....)
=>
(str 
  (clojure.string/upper-case 
   (first 
    (clojure.string/split (:name user/person) #" "))) "!!!!")

Java

Clojure

// Arrays
String[] movies = { "Star Wars",
                    "Star Trek",
                    "Bladerunner" };

// Lists (JDK 9+)
List.of(1,2,3);

// Sets
Set.of(1,2,3);

// Hashmaps
Map.of("brand", "Honda", "model", "Fit");

// many others
// also specialized ones for
// immutable, synchronized, lock-free coll.
;; Arrays i.e. vectors
(def movies ["Star Wars" "Star Trek" 
             "Bladerunner"])

;; Lists
'(1 2 3)

;; Set
#{1 2 3}

;; Map

{:brand "Honda", :model "Fit"}

;; colon denotes a keyword
;; i.e. similar to interned string
;; commas are whitespace

;; all datatypes are immutable by default
;; and lock-free

Data Structures

Java

Clojure

// Arrays
List<String> movies = List.of("Star Wars",
                    "Star Trek",
                    "Bladerunner");

movies.get(0)
=> "Star Wars"

movies.get(1)
=> "Star Trek"

movies.subList(1, movies.size())
=> ["Star Trek", "Bladerunner"]
// note: subList creates a 'view'

movies.get(2);
=> "Bladerunner"

movies.stream().limit(2)
  .collect(Collectors.toList());


// intermediate ops on streams
// are lazy
IntStream.iterate(0, x -> x + 1)
  .filter(x -> x % 2 == 0)
  .skip(10).limit(10)
  .boxed().collect(Collectors.toList())
=> [20, 22, 24, 26, 28, 30, 32, 34, 36, 38]
;; all collections are seqs 
;; (and implement Java coll interfaces)
(def movies ["Star Wars" "Star Trek" 
             "Bladerunner"])

;; following works for vectors, lists, maps, sets etc.
(first movies)
=> "Star Wars"

(second movies)
=> "Star Trek"

(rest movies)
=> ("Star Trek" "Bladerunner")

;; for vec, lists only
(nth movies 2) ;; (last movies)
=> "Bladerunner"

;; works for all seqs!
(take 2 movies)
=> ("Star Wars" "Star Trek")

;; seqs are lazy
(def all-nat-numbers (range)
(def all-even 
  (filter even? all-nat-numbers))

(take 10 (drop 10 all-even))
=> (20 22 24 26 28 30 32 34 36 38)

Working with Sequences

Java

Clojure

Map<String, Object> bestMovie = 
  Map.of("name", "Empire Strikes Back", 
         "year", 1980, 
         "actors", Map.of("Leia", "Carrie Fisher", 
           "Luke", "Mark Hamill", 
           "Han", "Harrison Ford"));


bestMovie.get("name");






Map<String, String> actors = 
  (Map<String,String>)bestMovie.get("actors");
// Warning: unchecked cast
actors.get("Leia")
=> "Carrie Fisher"

bestMovie.keySet();
=> [name, actors, year]

(def best-movie 
 {:name "Empire Strikes Back"
  :year 1980
  :actors {"Leia" "Carrie Fisher", 
           "Luke" "Mark Hamill", 
           "Han" "Harrison Ford"}})

(get best-movie :name)
=> "Empire Strikes Back"

;; keywords are also functions 
;; who look themselves up in maps!
(:name best-movie)
=> "Empire Strikes Back"


;; access nested maps:
(get-in best-movie [:actors "Leia"])
=> "Carrie Fisher"

(keys best-movie)
=> (:name :year :actors)

(select-keys best-movie [:name :year])
=> {:name "Empire Strikes Back", :year 1980}

Working with Maps

Java

Clojure

// add/update hash map
bestMovie.put("director", "Irvin Kershner");
=> Exception java.lang.UnsupportedOperationException
|        at ImmutableCollections.uoe (ImmutableCollections.java:71)
// oops: Map.of produces immutable HashMaps:
Map<String,Object> mutBestMovie = 
   new HashMap<>(bestMovie);
mutBestMovie.put("director", "Irvin Kershner");
=> null

mustBestMovie.remove("year");
=> null

;; "add/update" a key/value
(assoc best-movie :director "Irvin Kershner")
=> {:name "Empire Strikes Back", :year 1980, 
    :actors {"Leia" "Carrie Fisher", "Luke" "Mark Hamill", "Han" "Harrison Ford"}, 
    :director "Irvin Kershner"}

(dissoc best-movie :year)
=> {:name "Empire Strikes Back", 
    :actors {"Leia" "Carrie Fisher", "Luke" "Mark Hamill", "Han" "Harrison Ford"}}
    
;; where's Irvin?!?
best-movie
=> {:name "Empire Strikes Back", :year 1980, 
    :actors {"Leia" "Carrie Fisher", "Luke" "Mark Hamill", "Han" "Harrison Ford"}}
; yikes! Nothing changed!

; This is normal: Data is immutable by default!
; Changes like adding/removing/updating 
; creates new maps
; (they are stucturually shared and efficient)

Working with Maps 2

Immutable by default

(def a [1 2 3])

;; conj 'adds' a value to a collection
(def b (conj a 4))
=> [1 2 3 4]

;; conj adds according to data structure:
;; list - at front
;; vector - at end
;; map - undefined

(= a b)
=> false

a
=> [1 2 3]
;; a hasn't changed!
;; modifications always create copies
;; done efficiently thru structural sharing

Immutability

import java.util.stream.Stream;
import java.util.Collections;
import static java.util.stream.Collectors.*;

List<Integer> a = List.of(1,2,3);
// ⇧ immutable
// let's add a 4 to it
// should be easy!

List<Integer> b = Stream
   .concat(a.stream(),
           Stream.of(4))
   .collect(
     collectingAndThen(toList(), 
       Collections::unmodifiableList));

// trying to stay immutable
// collection classes not made for this
// wastes a lot of memory





Limited Support

Stateful

Immutable

class Person {
  String name; List<Person> friends;

  Person(String name) { this.name = name; }
  Person(String name, List<Person> friends) {
    this(name);
    this.friends = friends;
  }
  List<Person> getFriends() {
    return friends;
  }
  public String toString() { return this.name + 
    (this.friends != null ? ":" + this.friends : ""); };
  public static void main(String... args) {
    Person[] friends = { new Person("Ray"),
        new Person("Egon"),
        new Person("Winston") };
    Person peter = new Person("Peter",
        new ArrayList<>(Arrays.asList(friends)));

    Person dana = new Person("Dana", 
      peter.getFriends());
    System.out.println(dana);
    // peter shuns ray
    peter.getFriends().remove(0); // what happens?
    System.out.println(peter.getFriends());
    System.out.println(dana.getFriends());
  }
}
(def peter {:name "Peter" 
             :friends [{:name "Ray"}
                       {:name "Egon"} 
                       {:name "Winston"}]})
(def dana {:name "Dana" 
           :friends (:friends peter)}


(def bad-peter
  (update-in peter [:friends] rest))
=> {:name "Peter",
    :friends ({:name "Egon"} 
              {:name "Winston"})}

dana
=> {:name "Dana", 
    :friends [{:name "Ray"} 
              {:name "Egon"} 
              {:name "Winston"}]}

peter
=> {:name "Peter", 
    :friends [{:name "Ray"} 
              {:name "Egon"} 
              {:name "Winston"}]}

Immutable by default

Interfaces/Classes

Protocols/Records


interface Shape {
   int area();
}

class Rectangle implements Shape {
   int width;
   int height;
   Rectangle(int w, int h) { 
    width = w; height = h; 
   }

   int area() {
     return width*height;
   }
}

Rectangle r = new Rectangle(10,20);
r.area();
=> 200
(defprotocol Shape
  (area [this]))

(defrecord Rectangle [width height]
  Shape
  (area [this]
    (* width height)))
     
(def r (->Rectangle 10 20))

(area r)
=> 200

;; defrecord creates 
;; a new Java class on the fly
;; use records when you need values 
;; that support polymorphism 
;; equals/hashCode taken care for you

;; other ways 
;; to create java classes:
(deftype) ;; more control
(proxy) ;; inherit from existing classes

What about classes?

Single dispatch

class Circle implements Shape {
   int radius;
   Circle(int r) { radius = r; }
   public int area() {
     return radius * radius * Math.PI; // we'll stick to ints right now
   }
}

Shape s = new Circle(10);
s.area()
=> 314

// runtime knows that s is a Circle
// and calls the right area method
(defrecord Circle [radius]
  Shape
  (area [this]
    (* radius radius Math/PI)))
     
(def c (Circle. 10)) 


(area c)
=> 314.159

;; regular single dispatch on actual type

Polymorphism

Dynamic dispatch

;; dispatching can be dynamic!
;; based on: is-a? predicate

(defmulti class-based class)
;; calls function class ⇧ on dispatch
;; now define for which values of class we should dispatch

(defmethod class-based Circle [c] :its-a-circle)
;;                isa? ⇧
(defmethod class-based Rectangle [r] :its-a-rectangle)
(defmethod class-based String [r] :its-a-string)


(def r (Rectangle. 10 20))
(class-based r)
=> :its-a-rectangle

(def c (Circle. 10))
(class-based c)
=> :its-a-circle

(class-based "I'm totally not in this class hierarchy")
=> :its-a-string


MULTIMETHODS

Advanced topic: Dispatch on crazy stuff

;; dispatch on multiple values: vector of :make and :model
(defmulti tariff (juxt :make :model))

(derive ::mercedes ::foreign)
;; note: :: means keyword + current namespace i.e. namespace

(derive ::bmw ::foreign) (derive ::mercedes ::foreign)
(derive ::ford ::domestic)
(derive ::s-class ::any) (derive ::fusion ::any) (derive ::m3 ::any)

(defmethod tariff [::bmw ::i3] [car] (* (:value car) 1.20))
(defmethod tariff [::foreign ::any] [car] (* (:value car) 1.10))
(defmethod tariff [::domestic ::any] [car] (* (:value car) 0.95))
(defmethod tariff :default [car] (:value car))

(def car-1 {:make ::bmw :model ::i3 :value 20000})
(tariff car-1)
=> 24000.0

(def car-2 {:make ::mercedes :model ::s-class :value 24000})
(tariff car-2)
=> 26400.00

(def car-3 {:make ::ford :model ::fusion :value 12000})
(tariff car-3)
=> 11400.0

(def car-4 {:make ::trabant ::model 600 :value 1000})
(tariff car-4)
=> 1000


MULTIMETHODS 2

Java

Clojure

interface Lineup {
  void startPlayer(Player p);
}
class SoccerTeam implements Lineup {
   String name;
   Set<Player> lineup = new HashSet<>();
   public void startPlayer(Player p) {
    lineup.add(p);
   }
}
(defprotocol Lineup 
  (start-player [this ^Player p]))
(defrecord SoccerTeam 
  [^String name lineup]
  Lineup
  (start-player [this player]
    (update-in this [:lineup] 
       (fnil conj #{}) player)))
   

Working With Types

  • Types describe data layout and available methods
  • Compile-time safety that types are used properly
  • Assert statement
  • Runtime errors (downcast?)
  • Java type system available
  • Type hints (^Class) for performance
  • Runtime checks via Spec or Schema library
  • Runtime errors
SoccerTeam s = new SoccerTeam("1.FCS");

s instanceof SoccerTeam
=> true

s.getClass();
=> SoccerTeam

// anonymous classes
Lineup anon = new Lineup() {
   public void startPlayer(Player p) { } };




anon instanceof Lineup => true
s instanceof Lineup => true

anon.getClass().getSuperclass() 
=> class java.lang.Object
anon.getInterfaces(); 
=> Class[1] { interface Lineup }

// class.isAssignableFrom/isInstance...
(def s (->SoccerTeam "1.FCS" #{}))

(instance? SoccerTeam s)
=> true

(type s)
=> user.SoccerTeam


;; create instance of a new 
;; implementation on the fly
(def anon (reify Lineup
           (start-player [this p] ())))


(satisfies? Lineup anon) => true
(satisfies? Lineup s) => true

(supers (type rs))
=> #{clojure.lang.IObj 
     user.Lineup clojure.lang.IMeta 
     java.lang.Object}

(class? (class s))
=> true

Working with Types 2

;; extends existing types with new methods
(defprotocol TeamSize
  (size [this]))
(extend-type SoccerTeam
  TeamSize
  (size [this] (count (:lineup this))))

(def s (->SoccerTeam "1.FCS" #{"Lahm"))
(size s)
=> 1

;; works on Java types too!
(defprotocol WordCount 
  (count-words [sentence]))

(extend-type String 
  WordCount 
  (count-words [s] (count (str/split "hello world" #"\s"))))
;; #"..." is interpreted as regular expression

(count-words "this works!")
=> 2
;; we added a new 'method' to type String

Dynamic Types

Multi-threading

Basic primitives

new Thread(()
   -> System.out.println("Hello Thread")).start();
   
// Get result
// various approaches using Executors
// most concise:
CompletableFuture.supplyAsync(() -> 42).get();
==> 42


// Safe datastructures
// Atomic references (not using AtomicCounter here)
AtomicReference<Integer> atom = 
   new AtomicReference<>(0);
atom.getAndUpdate(v -> v+1);
==> 0
atom.get();
==> 1


// concurrent stream ops
List.of("a","b","c").stream()
  .parallel().map(String::toUpperCase)
  .toArray();
  ==> Object[3] { "A", "B", "C" }
(future (println "Hello World!"))


;; future returns a ref
;; use deref or @ to get the result
(deref (future 42))
==> 42
@(future 42)
==> 42

;; Clojure has refs, agents and atoms
;; atom are the simplest construct
(def a (atom 0))
(swap! a inc)
==> 1
;; ^^^ new value (Java returns old value)

@a
==> 1

;; pmap runs fns in parallel
(pmap str/upper-case '("a" "b" "c"))
==> ("A" "B" "C")

Calling Clojure

Calling Java

// add clojure.jar as dependency
// use clojure.java.api package

// lookup and call functions

IFn plus = Clojure.var("clojure.core", "+");
plus.invoke(2, 3);
=> 6

// in order to access other namespaces,
//  use require
IFn require = Clojure.var("clojure.core", 
"require");
require.invoke(Clojure.read("clojure.set"));

// Clojure.read turns a clojure expression
// into an object

// example of higher-order functions
IFn map = Clojure.var("clojure.core", "map");
IFn inc = Clojure.var("clojure.core", "inc");
map.invoke(inc, Clojure.read("[1 2 3]"));

;; full interop support

;; create new instance of class with `new`
;; Or use dot syntax
(def h (java.util.HashMap.))

;; call method: use . + method name
(.put h "foo" "bar")

;; access field: use .-
(.-x (java.awt.Point. 1 2))

;; static methods or field
(System/getProperty "java.home")

;; implement interface
(reify java.util.concurrent.Callable
      (call [this] :bubu))

;; extend class
(proxy [java.awt.event.MouseAdapter]
     [] ; args for super constructor
   (mousePressed [event] (print "click")))

;; generate new classes on the fly
(gen-class ...)
(deftype ...)

Interoperability

// calling subsequent methods

Map<String,String> m = new HashMap<>();

m.put("bubu", "lala");
m.put("foo", "bar");


// chaining method calls

System.getProperties().get("java.home");

// fail when value is null

Customer c = ...;
c.getPerson().getAddress().getZipCode();
// NPE when c.getPerson() == null 
// or c.getPerson().getAddress() == null!

Person p = c.getPerson();
if (p != null) {
  Address a = p.getAddress();
  if (a != null) {
    return a.getZipCode();
  }
}
// proposed solution: Optional<V>
;; useful short cuts when calling Java code

;; create an object and call methods on it
(doto (java.util.HashMap.)
      (.put "bubu" "lala") 
         ; vs. (.put h "bubu" "lala")
      (.put "foo" "bar"))
=> { "bubu": "lala", "foo": "bar" }


;; use .. to chain calls

(.. System (getProperties) (get "java.home"))


;; shortcut on nil, returns nil
;; or last result otherwise)
(def c (get-customer)
(some-> c (.getPerson) 
          (.getAddress) 
          (.getZipCode))
;; returns nil or the zip code

Interoperability 2

Java

Clojure

package com.acme.util;

import java.util.Map;
import static java.util.Collections.*;





public class A {}
protected class B {}
class C {}
private class D{}

// new with JDK9
// special module syntax
// restrict access to packages
// define dependencies 
//  (without version numbers)

module com.acme.util {
    requires java.base;
    exports com.acme.util;
}

(ns com.acme.util
"Documentation of the package"
;; similar to package statement, but not quite
;; expects code in com.acme/util.clj

;; declares dependencies and imports
;; require is for other namespaces
  (:require
   [clojure.string :as string])
;; i.e. instead clojure.string/trim
;; use string/trim 

;; import is for Java classes
  (:import
   (java.util Date Map List))

)
;; much more...

;; define vars and functions 
;; as private to a namespace:
(defn ^:private not-available-to-outsiders
  [] :ha!)
;; there's also the older defn- 

(def ^:private no-access :ha!)

Code Organization

Java

Clojure

// get Java, maven, gradle
brew cask install java
brew install maven
brew install gradle // ya probably need both

// mvn: create new project
// using archetypes

mvn archetype:generate 
  -DgroupId=com.mycompany.app 
  -DartifactId=my-app 
  -DarchetypeArtifactId=maven-archetype-quickstart 
  -DinteractiveMode=false

// gradle: init task
// limited templates available
// no custom templates
gradle init --type java-library

// Development Tools

// plenty choices:
// Intellij IDEA
// Eclipse
// Netbeans
// Emacs etc.

            
            
;; install Clojure
brew install clojure

;; Leiningen/Boot
brew install leiningen
brew install boot ;pick one

;; lein templates
;; app is standard template
lein new app my-fancy-app
;; many more on clojars.org

;; boot
boot -d boot/new new -t app -n my-fancy-app




;; Development Tools
;; Most active:
;; Emacs with CIDER package
;; Intelij IDEA with Cursive plugin
;; Atom with proto repl
;; LightTable
;; Nightcode

;; also:
;; Eclipse with Counterclockwise
;; Vim with Fireplace/Spacemacs

            

PROJECT SETUP

Change, Compile, Test

Read-Eval-Print

// if we are lucky we can hot reload classes
// but won't work in all cases

// Person.java:
public class Person {
  String first, last;
  Person(String first, String last) {
    this.first = first;
    this.last = last;
  }
  public String fullName() {
    return first + " " + last;
  }
}

// PersonTest.java
public class PersonTest { 
  @Test testPerson() {
     Person p = new Person("Han", "Solo");
     assertEquals("Han Solo", p.fullName());
   }
}

// compile
// run test
// if red, repeat
;; First: run a REPL
;; lein repl

;; person.clj
(defrecord Person [first-name last-name])

(defn full-name [person]
  (str (:first-name person) 
    " " (:last-name person)))

;; eval in REPL using IDE or simply:
(require 'person)
;; try stuff:
(def p (->Person "Han" "Solo"))
(full-name p)
=> "Han Solo"

;; made a mistake?
;; change person.clj and re-evaluate
;; just the changed function
;; your IDE supports this
;; or use
(require 'person :reload-all)

;; note: p is still defined!
;; or (refresh) to reload any changed namespace

Developing

Unit tests

Spec your data


// popular approaches:
// unit test with JUnit/TestNG

// Mocking data 
@Before
public void beforeEachTest() {
  personService = Mockito.mock(PersonService.class);
  when(personService.lookup("Han Solo")
   .thenReturn(new Person("Han", "Solo"));
}

@Test void getPerson() {
  Person p = personService.lookup("Han Solo");
  assertEquals("Han Solo", p.fullName());
}

// Property-based testing
// with junit-quickcheck
@Property public void concatLength(
     String s1,
     String s2) {
     assertEquals(s1.length() + s2.length(), (s1 + s2).length());
}
;; if you need a test suite:
;; clojure.test
(require '[clojure.test :refer :all])
(deftest full-name-test
  (let [p (->Person "Han" "Solo")]
   (is (= "Han Solo" (full-name p)))))
(run-tests)

;; spec: define predicates your data
;; and functions need to fulfill
(require '[clojure.spec.alpha :as s])
(s/def ::first-name string?)
(s/valid? ::first-name (:first-name p))
=> true

;; automatically generate random test data
(s/exercise ::first-name 5)
=> (["" ""] ["9" "9"] ["O9" "O9"] ["5" "5"] ["2uQ" "2uQ"])

(s/def ::person #(instance? Person %))
;; can also spec functions

(s/fdef full-name 
   :args (s/cat :person ::person?) :ret string?)
(stest/instrument `full-name)

(full-name "not a person")
=> "no person" fails spec: 
  [:args :person] predicate: 
     (instance? user.Person %)
 

Testing

Gradle/Maven

Leiningen/Boot/tools.deps.alpha

// libs mostly on mvn repos

// define dependencies
// mvn: 
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>3.8.1</version>
</dependency>

// gradle: 
dependencies {
    compile group: 'junit', name: 'junit', 
        version: '4.+'
    compile "joda-time:joda-time:2.2"
}
;; libs on maven, clojars.org

;; lein: project.clj - just clj code
:dependencies 
   [[org.clojure/clojure "1.9.0"]
    [com.sun.mail/javax.mail "1.5.4"]]

;; boot: build.boot - also just clj
:dependencies 
   [[clojure.java-time "0.3.1-SNAPSHOT"]]

;; tools.deps - part of Clojure 1.9
:deps { com.walmartlabs/test-reporting {
          :mvn/version "0.1.0"}}
;; also supports local and github dependencies

Building/Deploying

// define main entry point
public static void main(String... args) {}


// run interactively (JDK9)

jshell --somethingsomething ...

// run non-interactively
mvn exec:java
// mvn not really made for this

./gradlew run






// build
// both have support for uberjar as well
// if configured correctly
mvn package

gradle shadowJar
;; configure namespace to run
(defn -main [& args] ...)

lein: :main my.namespace

boot: jar {:main 'full-stack-boot-example.core}

;; run interactively

lein repl

boot repl

clj

;; run non-interactively

lein run


;; build
lein uberjar

boot uber

Running/Packaging

Enums

Just use a map

// Enums

enum Planet {
    MERCURY (3.303e+23, 2.4397e6),
    VENUS   (4.869e+24, 6.0518e6),
    ...
    private final double mass;   
    private final double radius;
    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
    }
    double mass() { return mass; }
    double radius() { return radius; }
}

Planet.VENUS.name()
=> "VENUS"

Planet.valueOf("VENUS")
=> VENUS

Planet.values()
=> { MERCURY, VENUS, ... }
;; no enums, but simple data + functions

;; one way to implement this
(def planets {
    :mercury [3.303e+23, 2.4397e6]
    :venus   [4.869e+24, 6.0518e6]
    ,,, })


(defn mass [planet]
    (first (planet planets)))

(defn radius [planet]
    (second (planet planets)))

(name :mercury)
=> "mercury"

(keyword "mercury")
=> :mercury


(keys planets)
=> (:mercury :venus)

Other Fun Stuff

Metadata as annotations

Metadata is just a map

// Create magic through annotations

class Customer {
  @Autowired Person person;
}

;; define metadata with ^{...}
;; which can also contain annotations

(deftype Customer [^{ Autowired true} person])

(map #(.annotationType %)
  (seq (.getAnnotations 
         (.getField 
            Customer "person"))))
=> (Autowired)

;; metadata can be added to any symbol

(def ^{:mass 3.303e+23 :radius 2.4397e6}
  mercury "I'm a planet"})

;; metadata is attached to the symbol, 
;; more specifically the Var 
;; the symbol points to
;; Var is a mutable reference

(:mass (meta (var mercury)))
=> 3.303E23

;; both annotations and metadata are sparingly used
;; often, higher-level functions take their role
;; example: (re-try 3 (timeout 1000 my-function))

Annotations?

Bind names to parts of your data-structure

;; extends binding expression to reach into data
(let [point [10 20]
      x (first point)
      y (second point))
;; left hand can be a destructuring expression:
(let [[x y] [10 20]])
;; assigns x to 10 and y to 20

;; works for argument lists too
(defn print-coords [[x y]]
  (println "x:" x ",y:" y))
(print-coords [10 20])
=> x: 10 ,y: 20

;; also works for maps with keywords and strings as key
(def p {:first-name "Han" :last-name "Solo"})

(defn full-name [person]
  (str (:first-name person) 
    " " (:last-name person)))
;; not satisfying to repeat person three times...

;; :keys takes list of symbols which are treated as keys and bound to the key's value
(defn full-name* [{:keys [first-name last-name]}]
  (str first-name " " last-name))
  
(full-name* p)
=> "Han Solo"

Destructuring

ADVANTAGES

  • Pure functions make code easier to test and change
  • Immutable data-structures are easy to reason about and inherently thread-safe
  • Thus achieving parallelism is almost trivial
  • REPL-assisted programming is more productive and leads to higher confidence in code produced
  • Significantly less lines of code: the best code is code you never have to write
  • Dynamic types that allow for simpler abstractions and code re-use
  • Still use all the Java goodies when run on the JVM
  • Got a front-end? ClojureScript compiles to JavaScript

Dealing with THE Disadvantages

 

  • Limited static code analysis i.e. no static types: Use spec or other libraries to validate data in the system at runtime. Use a linter (clj-kondo, eastwood)
  • Terse code: Run it in the REPL to see what it does
  • Higher hardware cost due to higher memory consumption: Optimize using transient collections if necessary and type hints
  • Confusing error messages: Greatly improved in 1.10
  • Limited talent pool: Good developers can be productive quickly with some guidance
  • But: Easy to pick up, long time to master

Real life Apps?

Cisco:  Cisco Threat Intel

Walmart: Various large scale services (SavingsCatcher etc.)

Cognitect: Datomic (Datalog based, immutable DB)

Nubank: complete banking system, 34m clients, 160 dev, uses Datomic, 80TB of data

Sandia National Labs: Data Integration

Zimpler: Payment solution

Soundcloud: Backend services

Zendesk: Data Analytics
AppFlyer: Ad-tech 70B events/day

Hundreds more:

https://clojure.org/community/companies

Main page:      https://clojure.org/

ClojureScript:  https://clojurescript.org/

Slack Community:

                          https://clojurians.slack.com

Zulip Chat:       https://clojurians.zulipchat.com/login

Many more:    https://clojure.org/community/resources

 

Clojure for Java Developers

By Jochen Bedersdorfer

Clojure for Java Developers

Side-by-side code examples of Java and Clojure.

  • 6,874