Clojure’s Mini-languages
It can be said that one of the strengths of Clojure is that it is comprised of many little mini-languages, each fulfilling a particular sweet spot. Based on a conversation on #clojure, I present here a list of Clojure’s internal mini-languages, with some examples of each:
Destructuring
One of the most powerful features that Clojure provides is that of destructuring. Simply put, destructuring refers to the way that collections can be pulled apart into local bindings.
(defn destr-seq [[a b & more]] (str "Got " a ", " b ", and " more))
(destr-seq [1 2])
;=> "Got 1, 2, and "
(destr-seq [1 2 3 4])
;=> "Got 1, 2, and (3 4)"
;; associative desctructuring
(let [{first-thing 0 last-thing 3} '[a b c d]]
[first-thing last-thing])
;=> [a d]
;; map destructuring
(let [{p1 :player1 p2 :player2} {:player1 "Mike", :player2 "Chris"}]
[p1 :vs p2])
;=> ["Mike" :vs "Chris"]
And there are many other ways to destructure in Clojure (including a facility for named-args coming soon), but I think there is little doubt that what I’ve shown counts as a mini-language.
List Comprehensions
Clojure list-comprehensions are performed using the for
macro:
(for [x (range 3), y (range 3), z (range 3)
:when (or (< x y z) (> x y z))]
[x y z])
;=> ([0 1 2] [2 1 0])
(for [x (range 3), y (range 3), z (range 3)
:while (or (< x y z) (> x y z))]
[x y z])
;=> ([2 1 0])
The for-comprehension also allows a binding form using the :let
directive.
Regular Expressions
Regular expressions are far from mini, but in the greater context of Clojure I’ll just name it as a mini-language:
(re-seq #"\w*(\w)" "one-two/three")
;=> (["one" "e"] ["two" "o"] ["three" "e"])
(re-find #"(?i)ninjas" "NiNJaS")
;=> "NiNJaS"
There is not much else to say about regular expressions.
#()
The short-hand function form is a particularly small mini-language, but likely fits the bill:
(def four-things #(list % %2 %3 %4))
(four-things 1 2 3 4)
;=> (1 2 3 4)
(#(apply %1 %&) str "Hello " "Cleveland")
;=> "Hello Cleveland"
Syntax-quote
Syntax quote is roughly analogous to the Lisp backquote; however, the former does some nice things to avoid name capturing. I will not go into that particular aspect, but you may be able to garner how with some examples:
`(+ 10 (* 3 2))
;=> (clojure.core/+ 10 (clojure.core/* 3 2))
`(+ 10 ~(* 3 2))
;=> (clojure.core/+ 10 6)
(let [x '(+ 2 3)] `(1 ~@x))
;=> (1 + 2 3)
`x#
;=> x__623__auto__
+
;=> #<core$_PLUS___3519 clojure.core$_PLUS___3519@6179d854>
`~'+
;=> +
`(~'+ 1 2 3)
;=> (+ 1 2 3)
(eval `(~'+ 1 2 3))
;=> 6
The above examples do no justice to the power of syntax-quote. Of course, any time you talk about it a discussion of macros soon follows, which provide a way for an infinite number of mini-languages to be created.
pre- and post-conditions
I talked about function constraints previously, so I will not cover that ground again. However, in summary they provide the building blocks for contract programming.
(defn constrained-fn [f x]
{:pre [(pos? x)]
:post [(= % (* 2 x))]}
(f x))
(constrained-fn #(* 2 %) 2)
;=> 4
(constrained-fn #(float (* 2 %)) 2)
;=> 4.0
(constrained-fn #(* 3 %) 2)
;=> java.lang.Exception: Assert failed: (= % (* 2 x))
Sean Devlin also created a screencast taking pre- and post-conditions one step further.
-> and ->>
I’ve also talked about Clojure’s pipeline macro ->
, but did not show the ability to nest:
(-> 25 Math/sqrt (->> (- 3) str))
;=> "-2"
Literal Numerics
These are all the same number:
[127 0x7F 0177 16r7F 2r01111111]
;=> [127 127 127 127 127]
ns
Namespace declarations are very declarative in nature:
(ns joy.ns-ex
(:refer-clojure :exclude [defstruct])
(:use (clojure set xml))
(:use [clojure.test :only (are is)])
(:require (clojure [zip :as z]))
(:import (java.util Date)
(java.io File)))
gen-class
The gen-class
feature is likewise declarative in nature, both in their stand-alone form, and their ns
-embedded (which would make them an internal-internal-mini-language) form:
(ns clojure.examples.instance
(:gen-class
:implements [java.util.Iterator]
:init init
:constructors {[String] []}
:state state))
A much better description of gen-class can be found on Meikel Brandmeyer‘s blog.
Macros
Many of these so-called mini-languages are possible thanks to Clojure’s macros. While not really a mini-language, macros are the building blocks of your own.
What mini-languages does your programming language have?
-m
5 Comments, Comment or Ping
oh well
Clojure had good beginnings as a Lisp but has really deteriorated into a syntactic sugar soup, by some measures even worse than Perl. At this point the only real alternative is Scheme; both Gambit-C and Chicken Scheme are quite good. As an added benefit, these two Scheme implementations compile to native code, unlike Clojure which relies on the semi-interpreted JVM.
Mar 23rd, 2010
fogus
@oh_well
I hope you do not judge the whole of Clojure by this blog post. While it’s true that Clojure offers all of these forms and these forms are dense with information; it’s not true that you would see them in such close proximity. I also like Scheme, so if you happen to decide against Clojure in favor of Scheme then I hope you come back and post some comparisons. :m
Mar 23rd, 2010
Mec
-> and ->> are not actually nesting:
user> (->> 25 Math/sqrt -> (- 3) str) “-2.0” user> (->> 25 Math/sqrt (- 3) str) “-2.0”
-> is just being ignored
Mar 24th, 2010
fogus
Yes, of course you’re right. Thanks for pointing that out. :m
Mar 24th, 2010
Fred
Since clojure 1.2 cl-format https://clojuredocs.org/clojure.pprint/cl-format might be another one :-)
Sep 16th, 2015
Reply to “Clojure’s Mini-languages”