Jochen Bedersdorfer
Software Engineer
Jochen Bedersdorfer (@beders)
Go down for more details ➠
vs.
Stateful objects: Objects send messages between each other
Functions transform data
class Hello {
public static void main(String... args) {
System.out.prinln("Hello Java");
}
}
(println "Hello Clojure")
$ javac Hello.java
$ java Hello
$ clj hello.clj
// 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
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 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
;; 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!!!!"
Extend syntax of Clojure
(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
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
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"}]}
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
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
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
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)))
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
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 ...)
// 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
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!)
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
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)
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))
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"
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:
Zulip Chat: https://clojurians.zulipchat.com/login
Many more: https://clojure.org/community/resources
By Jochen Bedersdorfer
Side-by-side code examples of Java and Clojure.