Clojure
Clojure and Leiningen both available in Nix.
Not sure about Clojure packages?
Leiningen
Use lein new app app-name
to create an application.
Use lein repl
to run things interactively.
Emacs
Install clojure-mode, cider.
Cider can create a you repl where you can play with your code.
It also has debugging and code completion.
Namespaces
Use ns form to create a namespace. :require imports Clojure libraries and :import imports Java libraries.
(ns project.thing "Some documentation about my namespace" ;; Get some functions from clojure.core (defaults to all of them) (:refer-clojure :exclude [some functions]) ;; Loads some libraries. (:require [clojure.set :as set]) ;; Loads some Java classes (:import [java.util.Date]))
All of the things inside ns have functions which you can use outside it, but we'll use ns for readability.
I'm not really clear on :use. I've seen advice not to use it though.
Pre and post-conditions
Pre-conditions go after the variable declaration.
(defn thing [n] {:pre [(number? n)]} (* 2 n))
Control flow
Most control flow things have -not versions, like when-not, if-not.
Also see if-let, when-let.
Not equal is not=.
Predicates end in ?, like empty?.
Case statements have three versions:
- case for compile-time constants
- condp for the same condition applied with multiple different values
- cond for an arbitrary condition
Strings
Concat with str.
Numbers
Remembers ratios. How does it decide to do this instead of floating point?
Keywords
Must begin with :, e.g. :fred
.
Can use a double colon ::fred
to look up in the current namespace.
Functions
Anonymous functions are constructed with #(). % or %1 %2 %3 … are used to fill in the arguments.
Varargs is represented with &: [a b c & args]
.
You can provide multiple signatures for a function. By surrounding the args and body with parentheses:
(fn multi-arity-add ([a] (multi-arity-add a 0)) ([a & args] (apply + (conj args a))))
Higher Order Functions
complement negates a predicate.
comp composes functions.
partial partially applies functions with some other arguments.
Usually, when composing or partially applying, I like to give the result a name.
Multimethods
Use defmulti to set up a multi-method with a dispatching function.
defmethod creates the individual parts of a multimethod (implementations which will be dispatched to). remove-method is its counterpart.
prefer-method lets you define which method wins.
You can use your Java hierarchy. Alternatively, you can use derive to invent one.
Threading Macros
These are -> and ->>.
-> is a convenient way to chain some function calls on a piece of data. The data returned from the previous function in the chain will be inserted into the first position. It's basically the owl operator %>%
from dplyr.
->> is similar, except that the data is inserted in the final position in each case.
as-> is similar again, except that you specify a name for your return value, and put that name in each function call.
some-> and some->> add null checks. If any return value is null, the remaining functions won't be called.
cond-> and cond->> lets you specify pairs of /test, function). Functions next to failing tests will be skipped.
Destructuring
Inside a variable binding (e.g. let, fn, loop), we can save ourselves some work.
Later lines can refer to early ones: (let [a thing b a])
.
We can extract structure from sequences. Each symbol should match an element. A symbol following & matches anything remaining. A symbol following :as matched the whole, original value. (let [a thing [b c _ & leftovers :as whole-list] a)
.
Missing values are bound to nil, extra values are ignored.
You can nest your destructuring arbitrarily, in which case & and :as will apply at the level they are used.
Using keys from maps: (let [a thing {b :b c :c d :d} a])
. In this case, we can supply default values with a special :or key. We can use the special :keys symbol to type less. (let [{:keys [a b c]} my-map)
.
Variables
Use def to define a variable initially.
Use alter-var-root to redefine it.
Privacy
Attach privacy metadata with ^:private
immediately after the def, defn or whatever.
Collections
Construct them as follows:
- a set with #{} or set()
- a map with {}, remembering to use :symbols as keys. You don't need commas, although they might help.
- a list with '() or list
- a vector with [], vec or vector
conj does the quickest insertion operation on a collection. For a list, this is prepend, while for a vector it's append. It can also apply to maps, in which case you need to pass it a list of pairs.
Sets are a function which returns whether or not the element is present
Passing a map to a symbol as an argument returns the value for that key, e.g. (:b {:a 10 :b 20})
.
May keys may be namespaced, in which case you should construct the map as #:my-namespace{}
.
Transient
transient lets you make a copy of a collection for mashing on. You make some changes, and then call persistent!.
This can get you some performance benefits.
Seq
Seq is an interface for lazy iteration.
doseq
is nested for loops.
Transducers
Transducers are a kind of data pipeline. If you squint at them, they look a big like C# LINQ or Powershell's BEGIN, PROCESS and END. Perhaps also a bit like monads.
Anyway, the point is you can compose some data transformations in a nice way, and separate out the cruddy bits (exception handling, IO).
I didn't fully understand the explanation though, may need to do more thinking about this, or just try them out.
A transducer takes a reducing function (of the form (fn [acc el] function-body )
and spits out a new one.
Many sequence functions produce a transducer if the sequence argument is omitted.
Transducers are composable. When you do so, the operations will happen left-to-right.
The transduce function is like reduce, but with an extra slow for your composed transducers at the front.
The eduction function takes some transducers and a collection and returns a map/reduce interface to that collection.
into, which fills a collection, has a slot for transducers. So does sequence.
Transducers usually define start/stop/complete phases with 0/2/1 arities.
Java Interop
Avoid writing new.
Static methods are written with a /, like Math/pow
.
edn
edn is a serialization format. https://github.com/edn-format/edn
Data Structures
deftype and defrecord create classes. You get a constructor, which you can access with #my.thing[1 2 3]
.
deftype is the simple version. It has mutable fields.
defrecord implements an immutable map. You can initialize it with #my.record{:a 1, :b2}
.
It's usually better to use defrecord.
reify makes a one-off instance of an anonymous class. There's also a less-strict version called proxy.
defstruct is obsolete.
Protocols
These are like interfaces, except that they don't create a type hierarchy.
Create them with defprotocol, a name and a list of functions with arguments.
All the functions you define here will dispatch on the type of their first argument.