Sync'ing from Memory

Clojure

Clojure Notes - An aimless dump of anything and everything interesting I find and manage to record.

Introduction

Interesting code/styles I've found in various Clojure open-source codebases.

Interesting Styles   style

Optional data

This is from the Sente library by Peter Taoussanis. Taking an example from sente itself - a server event message is a map expected to have multiple keys, as below. Since we are in Lisp-land, almost every character on the keyboard is available for identifiers.

  {:keys [event id ?data send-fn ?reply-fn uid ring-req client-id]}

We start the identifier with a ? - something that we use to indicate optional. So, keys ?data and ?reply-fn represent optional entries in the map.

Splitting a namespace across files

When your namespace is growing too large for a single file, you can split it across multiple files. For example, take the clojure.core namespace, which is split across multiple files. As a more concrete piece, core_print.clj contains some clojure.core functions. Here's how it is done - notice that the base file-name, without the .clj suffix, is used.

  ;; In core.clj
  (load "core_print")

Inside core_print.clj, use the in-ns directive to tell the clojure reader/compiler how to treat the code in here.

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; In core_print.clj, the beginning line is
  (in-ns 'clojure.core)

  ;; And the implementation follows

Smart Tricks   snippets

Convert a map (keyed by integers) into a correspondingly ordered vector

;; From clojure.edn.data
(ns user)
(defn- vectorize
  "Convert an associative-by-numeric-index collection into
   an equivalent vector, with nil for any missing keys"
  [m]
  (when (seq m)
    (reduce
     (fn [result [k v]] (assoc result k v))   ;; 1
     (vec (repeat (apply max (keys m)) nil)) ;; 2
     m)))

(def m {0 :A 2 :C 9 :J})
(vectorize m)
;; Output #'user/vectorize[:A nil :C nil nil nil nil nil nil :J]
#'user/vectorize
#'user/m
[:A :C :J]

On line marked 2, you initialize a vector of the required length with nil-s, using max

  (apply max (keys m))
9

And reduce m with the function defined on line marked 1 into this vector.

Temporarily descending into mutable-land.   performance

Immutability is great, but it's okay to mutate within the confines

  (defn filterv
    "Returns a vector of the items in coll for which
    (pred item) returns true. pred must be free of side-effects."
    {:added "1.4"
     :static true}
    [pred coll]
    (-> (reduce (fn [v o] (if (pred o) (conj! v o) v))
                (transient [])
                coll)
        persistent!))
#'user/filterv

conj! adds an element to the transient vector - notice the ! at the end. Also, the transient-functions always return a reference that you are expected to use for the next step. This is important - even though it mutates in place, we are not supposed to hold on to any old reference while building the transient datastructure. And finally, the collection is made persistent with persistent!.

Control Flow

An interesting discussion can be found at How are clojurians handling control flow on their projects?

Uncommon Stuff

deftype and Mutable Members

deftype allows for the definition of mutable members using the ^:volatile-mutable tag on a member. But they become private to the object, and we need to implement methods on them (via interfaces or protocols) to access these private members for modification.

(ns msync.deftype)

(defprotocol BarBazOperators
  (get-bar [this])
  (bar-inc! [this])
  (baz [this])
  (baz-inc! [this]))

(deftype FooBarBaz [foo ^:volatile-mutable bar ^:volatile-mutable baz]
  BarBazOperators
  (get-bar [_] bar)
  (bar-inc! [_] (set! bar (inc bar)))
  (baz [_] baz)
  (baz-inc! [_] (set! baz (inc baz))))

(def foo-bar-baz (FooBarBaz. 10 20 30))
BarBazOperators
msync.deftype.FooBarBaz
#'msync.deftype/foo-bar-baz
;; We have a problem
(.bar foo-bar-baz)
class java.lang.IllegalArgumentException

These are method calls.

(.get-bar foo-bar-baz)
(.baz foo-bar-baz)
20
30

So are these too - method calls.

(bar-inc! foo-bar-baz)
(get-bar foo-bar-baz)
(baz-inc! foo-bar-baz)
(baz foo-bar-baz)
21
21
31
31

But foo is different.

(.foo foo-bar-baz)
10

There is no method foo

(foo foo-bar-baz)
class clojure.lang.Compiler$CompilerException

&env in Macros

Inside of macros, you have access to the context in which you are being evaluated.

(defmacro show-env []
  (into [] (map class (keys &env))))
(defn show-env-wrapper [x y] (show-env))
(show-env-wrapper 10 20)
#'user/show-env
#'user/show-env-wrapper
[clojure.lang.Symbol clojure.lang.Symbol]

Although, it is an extremely tricky place to be in. map is lazy, so let's try to return the result of the map operation rather than the call to (into []..)

(defmacro show-env []
  (map class (keys &env)))
(defn show-env-wrapper [x y] (show-env))
(show-env-wrapper 10 20)
#'user/show-env
#'user/show-env-wrapper
class java.lang.ClassCastException

Nope. Did not go well.

Let's try getting the keys out.

(defmacro show-env []
  (into [] (keys &env)))
(defn show-env-wrapper [x y] (show-env))
(show-env-wrapper 10 20)
#'user/show-env
#'user/show-env-wrapper
[10 20]

Wut!? The keys actually turn out to be the vals! What if we used vals instead of keys (non-lazy)?

(defmacro show-env []
  (into [] (vals &env)))
(defn show-env-wrapper [x y] (show-env))
#'user/show-env
class clojure.lang.Compiler$CompilerException

Nope - we can't get the vals to escape. What are those?

(defmacro show-env []
  (into [] (map class (vals &env))))
(defn show-env-wrapper [x y] (show-env))
(show-env-wrapper 10 20)
#'user/show-env
#'user/show-env-wrapper
[clojure.lang.Compiler$LocalBinding clojure.lang.Compiler$LocalBinding]

Turns out - a core Compiler artifact - an inner class LocalBinding.

The keys are Symbol objects. Printing them gets us the values held in them. Ok, we can get all we want from the keys. Here's the culimnation (final version of show-env via @codesmith on the Clojurians Slack, which was also the inspiration for this section on &env)

(defmacro show-env []
  (into {} (map (juxt (comp keyword name) identity) (keys &env))))
(defn show-env-wrapper [x y] (show-env))
(show-env-wrapper 10 20)
(let [a-for "Apple"
      b-for "Bat"]
  (show-env-wrapper 100 200))
(let [a-for "Apple"
      b-for "Bat"]
  ((fn [x y] (show-env)) 1000 2000))
#'user/show-env
#'user/show-env-wrapper
{:x 10, :y 20}
{:x 100, :y 200}
{:a-for "Apple", :b-for "Bat", :x 1000, :y 2000}

As you can notice above, macros do their things at compile time. The first let block bindings aren't available to the show-env-wrapper function. The second let block's bindings, which uses an inline function, are available via the &env processing.

Java Interop

Some useful libraries

Leiningen

It's macro-land, after all

If you'd like to declare some constants at the top of your project.clj for reuse within defproject

(def my-version "1.2.3")

(defproject org.msync/my-pet-project ~my-version
  ;; And all your directives
  )

The eval-reader for dynamism

Some self-evident example code. Notice the use of the eval reader #=.

:source-paths [#=(eval (str (System/getProperty "user.home") "/.lein/dev-srcs"))
               "src"]

You can unlimit your imagination and find quite a few uses.

Gotchas

with-redefs

with-redefs and inlined-annotated vars don't play well.

  (with-redefs [+ -] (+ 10 20 30))
60

+, - and other functions (many math operations, among others) fall under this category. For reference, here is the source of + in Clojure 1.10.3

  (defn +
    "Returns the sum of nums. (+) returns 0. Does not auto-promote
    longs, will throw on overflow. See also: +'"
    {:inline (nary-inline 'add 'unchecked_add)
     :inline-arities >1?
     :added "1.2"}
    ([] 0)
    ([x] (cast Number x))
    ([x y] (. clojure.lang.Numbers (add x y)))
    ([x y & more]
       (reduce1 + (+ x y) more)))

Monads

Since no article is complete without an attempt at monads, we refer to Monads for completeness' sake.

This is only a re-run of 05-monads.

Monads refer to a category of values associated with a pair of functions

  1. unit

  2. bind

which have certain properties, as described below.

First, let us introduce some terminology and symbols

Category

The category to which our monads belong.

x

Any value

m

A value belonging to the Category

f

An arbitrary function, that returns a value belonging to the Category

?

(unit x) results in a value that belongs to this category, given any x

?

(bind f m) returns a value in the said category