Clojure for Java Developers


Jochen Bedersdorfer (@beders)

  • 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
  • Change how you think about data
  • Write pure functions that test easily
  • Be aware of side-effects
  • Value values
  • See time as transition between values
  • Prefer bottom-up coding to top-down coding
  • Avoid complexity
class Hello { public static void main(String... args) { System.out.prinln("Hello Java"); }
}
 (println "Hello Clojure")

$ javac Hello.java

$ java Hello

// 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
Math.min(1,2,3) // Methods attached to classes, objects
// arguments are comma separated
(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
Math.min(1,2, Math.max(3,4)) 
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
// 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)) ;; [] denotes a vector (vs a list)
;; closest to Java version: don't use this :)
;; for is list comprehension, not iterative loop
;; this has a return value: minimum match
=> (\V \o)

Methods/Lambda-Expressions

class Rectangle { static int area(int width, int height) { return width * height; }
} // lambda IntBinaryOperator op = (width,height) -> width*height; op(1,2) // ooopps
| Error:
| cannot find symbol
| symbol: method op(int,int)
| op(1,2)
| ^^ 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) 
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 lib
;; str/upper-case is a proper function (defn make-counter [start] (let [counter (atom start)] (fn [] (swap! counter inc))))) ;; atom is safe, shareable state
;; swap! applies inc to counter
;; and stores result in counter (def cnt (make-counter 10)) (cnt)
=> 11
(cnt)
=> 12
;; thread-safe by default ;; 'singleton'
(def singleton (memoize (fn [] :singleton)))
;; 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 ...

;; 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!!!!" 
(macroexpand '.....)
=>
(str (clojure.string/upper-case (first (clojure.string/split (:name user/person) #" "))) "!!!!")
// 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 
// 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
(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)
Map<String, Object> bestMovie = Map.of("name", "Empire Strikes Back
", "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" 
(def best-movie {:name "Empire Strikes Back" :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" 
(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 
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 
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"}]}

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
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)) ; no base type specified... (area c)
=> 314.159 ;; regular single dispatch on actual type 
;; 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 

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 
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))) 
  • Types describe data layout and available methods
  • Compile-time safety that types are used properly
  • Assert statement
  • Runtime errors (downcast?)
  • No compile-time types
  • Java type system available
  • Type hints 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
;; 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
// 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 ...)
// 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 
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!)
// 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 
// 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
// many teams aim for high test-coverage
// however, each unit test is code to maintain
// aim for writing the RIGHT tests, // not the most // 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 %) 

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 
// 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 
// 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) 
// 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 

Bind names to parts of your data-structure

;; expands 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 keywords
(defrecord Person [first-name last-name])
(def p (->Person "Han" "Solo"))
(:first-name p)
=> "Han" (defn full-name [person] (str (:first-name person) " " (:last-name person)))
;; not satisfying to repeat person three times... (defn full-name* [{:keys [first-name last-name]}] (str first-name " " last-name))
  • 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
  • Limited static code analysis i.e. no static types: Use spec or other libraries to validate data in the system at runtime
  • 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
  • Confusing error messages: Constantly being improved, usually easily googled
  • Limited talent pool: Good developers can be productive quickly with some guidance
  • But: Easy to pick up, long time to master

Cisco:  Cisco Threat Intel

Walmart: Various large scale services (SavingsCatcher etc.)

Cognitect: Datomic (Datalog based, immutable DB)

Nubank: complete banking system, 3m clients, 160 dev, uses Datomic

Sandia National Labs: Data Integration

Zimpler: Payment solution

Soundcloud: Backend services

Zendesk: Data Analytics

Hundreds more:

https://clojure.org/community/companies