cl-cairo2 was one of the first Common Lisp libraries I wrote, but I haven't been using it much for the last year or so (currently I am experimenting with cl-pdf as a backend for my new plotting library, which will be released soon). I have been pretty busy with research, so I didn't have time to merge (and test) patches, also, I didn't even contemplate updating the library to make use of the latest version of cairo. So when Ryan Pavlik contacted me about adding compatibility with cl-freetype2, I asked him whether he wants to take over as a maintaner. He kindly agreed, so I have transferred the repository to him, and he already merged a lot of patches.
One last thing that I wanted to fix before handing the repository over is the license. Originally, the library was licensed under the GPL — in retrospect, I think that
Consequently, I received many complaints about the license of the library, and decided to change it. I picked the Boost Software License, and contacted all who contributed to the library for permission. All contributors approved the change, so now the library has a simple, permissive non-copyleft free software license. However, it is always possible that I missed someone, so if you contributed to cl-cairo2 in the past but didn't hear from me regarding the license change, please get in touch (eg via the Github issue tracker).
I would like to thank (in alphabetical order) Ala'a Mohammad Alawi, Jay Bromley, Pau Fernández, Johann Korndoerfer, Peter Mikula, and especially Kei Suzuki (who did the last major reorganization) for their contributions to the library (again, I apologize if I missed anyone). I would also like to thank Ryan for taking over — I am convinced that the library is in good hands.
The Shuffletron music player, in various branches, has accumulated some neat features (particularly last.fm scrobbling in Brit Butler's branch) that deserve merging, and ought to be cleanly separated from the core of the program. Leslie Polzer sent me a novel implementation early on which used generic functions for extensibility, adding/removing methods via the MOP as plugins load/unload. Clever as that was (and I'm impressed how little code is required, rereading the patch now), I wasn't comfortable with it, and the lack of a pressing need for a plugin interface let me put it off for a good long while.
Building extensibility around generic functions seemed the right thing to do though, and a slightly different idea, of writing plugins in the style of mixins and calling CHANGE-CLASS to enable them at runtime, stuck in the back of my head until (with some prodding) I was motivated to try it out. It's hardly a new idea (both Gsharp and McCLIM contain implementations of similar ideas, as does the AMOP book, just to name a few examples), and a minimal implementation doesn't take much code at all:
(defvar *configurations* (make-hash-table :test 'equal))
(defun configuration (plugins)
(or (gethash plugins *configurations*)
(setf (gethash plugins *configurations*)
(make-instance 'standard-class
:name (format nil "MY-APPLICATION~{/~A~}" plugins)
:direct-superclasses (cons (find-class 'my-application)
(mapcar #'find-class plugins))))))
(defun reconfigure (application plugins &rest; initargs)
(apply #'change-class application (configuration plugins) initargs))
(defun active-plugins (instance)
(mapcar #'class-name (rest (sb-mop:class-direct-superclasses (class-of instance)))))
(defun enable-plugin (application plugin &rest; initargs)
(apply #'reconfigure application (adjoin plugin (active-plugins application)) initargs))
(defun disable-plugin (application plugin)
(reconfigure application (remove plugin (active-plugins application))))
(defun make-application (&rest; initargs)
(apply 'make-instance (configuration '()) initargs))
This isn't an ideal implementation, and there's a limit to how good it's going to get when CLOS doesn't fully support anonymous classes. However, a more serious attempt should work on arbitrary classes, provide a place to hang init/shutdown code for plugins, and the ability to list which plugins are enabled within an instance.
Piddling-plugins adds these features using only slightly more code than above, along with some superfluous macro magic for writing defun-style definitions that are extensible by plugins. I've made light use of it in a branch of Shuffletron, confirming to myself that it's a good fit.
The code is tiny and self-explanatory, so I'll just post the examples from the README file for fun.
Imagine we have written a music player, looking something like this deliberately simplified code:
(defclass music-player () ())
(defun run-music-player ()
;; You need to set or bind *application* to your application instance
;; if you use defun-extensible. It's a good idea even if you don't.
(let ((*application* (make-instance 'music-player)))
(init-audio)
(init-library *application*)
(loop (execute-command (read-line)))))
Functions extensible by plugins can be defined using DEFUN-EXTENSIBLE.
This is just syntactic sugar for defining a generic function specialized
on the application object, with a wrapper that passes in the value of *APPLICATION*.
(defun-extensible execute-command (command)
...)
(defun-extensible play-song (song)
...)
(defun-extensible song-finished (song)
...)
Plugins extend the behavior of the application by defining methods on the extensible functions (or rather the generic functions defined behind the scenes, which are prefixed by "EXTENDING-"):
(defclass scrobbler ()
((auth-token :accessor auth-token)))
(defmethod plugin-enabled (app (plugin-name (eql 'scrobbler)) &key &allow-other-keys)
(setf (auth-token app) (get-auth-token))
(format t "~&Scrobbler enabled.~%"))
(defmethod plugin-disabled (app (plugin-name (eql 'scrobbler)))
(format t "~&Scrobbler disabled.~%"))
(defmethod extending-song-finished :after ((plugin scrobbler) song)
(scrobble song (auth-token plugin)))
(defclass status-bar () ())
(defmethod extending-play-song :after ((plugin status-bar) song)
(redraw-status-bar))
(defmethod extending-execute-command :after ((plugin status-bar) command-line)
(declare (ignore command-line))
(redraw-status-bar))
To enable a plugin:
(enable-plugin *application* NAME [INITARGS...])
To disable a plugin:
(disable-plugin *application* NAME)
To set precisely which plugins are enabled:
(reconfigure *application* LIST-OF-PLUGINS [INITARGS...])
This blog has been going for five years. Back then, it seemed like the only widely-used static blog generators were Blosxom or pyBlosxom. They weren’t that hard to set up, but getting everything right rather than good enough is a lot of work. Latex and MathML support was also very weak, so I wound up using a (insane) one-off hack with tex4ht. I feel like Octopress and MathJax now do everything I need out of the box, better than anything I could design by myself.
The permalinks from the old blog are still around, but not the rss feeds or the date-based links.
I figure this is a good opportunity to make sure the (marginally useful) permalinks are available somewhere else than via google.
Another way to accumulate data in vectors
describes a copying-free extendable vector. The advantage over the
usual geometric growth with copy is that the performance with respect
to the number of elements added is much smoother. Runtimes are then
more easily predictable, and sometimes improved (e.g. right when a
copy would be needed). It’s also more amenable to a lock-free
adaptation, while preserving O(1) operation complexity (assuming that
integer-length on machine integers is constant time), as shown in
Dechev et al’s “Lock-free Dynamically Resizable Arrays”.
Common Cold is a really old hack to get serialisable closures in SBCL, with serialisable continuations built on top of that. Nowadays, I’d do the closure part differently, without any macro or change to the source.
Concurrency with MVars has short and simple(istic) code for mvars, and uses it to implement same-fringe with threads.
Constraint sets in SBCL: preliminary exploration summarises some statistics on how constraint sets (internal SBCL data structures) are used by SBCL’s compiler.
SBCL’s flow sensitive analysis pass explores what operations on constraint sets actually mean. This, along with the stats from the previous post, guided a rewrite, not of constraint sets, but of the analysis pass that uses them. The frequency of slow operations or bad usage patterns is reduced enough to take care of most (all?) performance regression associated with the original switch to bit-vector-based constraint sets, without penalising the common case.
Finalizing foreign pointers just late enough is a short reminder that attaching finalizers to system area pointers isn’t a good idea: SAPs are randomly unboxed and consed back, like numbers.
Hacking SSE Intrinsics in SBCL (part 1) walks through an SBCL branch that adds support for SSE operations. Alexander Gavrilov has kept a fork on life support on github. There’s still no part 2, in which the branch is polished enough to merge it in the mainline.
In the meantime,
Complex float improvements for sbcl 1.0.30/x86-64
built upon the original work on SSE intrinsics to implement operations
on (complex single-float) and (complex double-float) with SIMD
code on x86-64. That sped up most complex arithmetic operations by
100%. That work also came with support for references to unboxed
constants on x86oids; this significantly improved floating point
performance as well, for both real and complex values.
Initialising structure objects modularly is a solution to a problem that I hit, trying to implement non-trivial initialisation for structures, while allowing inheritance. Tobias Rittweiler points out that the protocol is very similar to a common CLOS pattern where, instead of functions that allocate objects, class designators are passed. It also looks a bit like the way Factor libraries seem to do struct initialisation, but with actual initialisation instead of assignment (which matters for read-only slots).
An Impure Persistent Dictionary is an example of a technique I find really useful to implement persistent versions of side-effectful data structures. Henry Baker has a paper that shows how shallow binding can be used to implement persistent arrays on top of functional arrays, with constant-time overhead for operations on the latest version. It’s a really nice generalisation of trailing in backtracking searches. Here, I use it to get persistent hash tables in only a couple dozen lines of code.
Pipes is an early attempt to develop a DSL for stream processing, like an 80% SERIES. I’ve refocused my efforts on Xecto, which only handles vectors, rather than potentially unbounded streams. The advantage is that Xecto looks like it has the potential to be simpler while achieving near-peak performance to me; the main downside is that vectors don’t allow us to represent control flow as data via lazy evaluation… and I’m not sure that’s such a bad thing.
The post on
string-case is an
overview of how I structured a CL macro to dispatch that compares with
string= instead of eql. If I were to do this again, I’d probably
try and improve string=; I later tested an SSE comparison routine,
and it ended up being, in a lot of cases, faster and simpler (with a
linear search) than the search tree generated by string-case.
The type-lower-bound branch
describes early work on a branch that provides a way to shut the
compiler up about certain failed type-directed optimisations. A lot
of the output from SBCL’s compiler amounts to reports of optimisations
that couldn’t be performed (e.g. converting multiplication by a
constant power of two to a shift), and why (e.g. the variant argument
isn’t known to be small enough). Sometimes, there’s nothing we can do
about it: we can’t show the compiler that the argument is small enough
because we know that it will sometimes be too large! Yet, CL’s type
system (like most) does not let us express that information.
Programmers are expected to provide upper bounds on the best static
type of values (e.g. we can specify that a value is always a fixnum,
although it may really only be integers between 0 and 1023). We would
like a way to specify lower bounds as well: “I know that this will
take arbitrary fixnum values.” Once we have that, the compiler can
skip reporting optimisations that we know can’t be performed (as
opposed to those we don’t know whether they can be performed).
Finally, Yet another way to fake continuations sketches a simple but somewhat inefficient way to implement continuations for pure programs. It may be useful for IO-heavy applications (web programming), or in certain cases similar to backtracking search, but in which most of the work is performed outside of backtracking (e.g. during constraint propagation).
SWAR implementation of (some #’zerop …) sketches how we can use SIMD-within-a-register techniques to have fast search for patterns of sub-word size. A degenerate case is when we look for 0 or 1 in bit vectors; in these case, it’s clear how we can test whole words at a time. The idea can be extended to testing vectors of 2, 4, 8 (or any size) -bit elements. I haven’t found time to move this in SBCL’s runtime library (yet), but it would probably be a neat and feasible first project.
Revisiting VM tricks for safepoints explores the performance impact of switching from instrumented pseudo-atomic code sequences to safepoints. The bottom line is that it’s noise. However, some members of the russian Lisp mafia have used it as inspiration, and have managed to implement seemingly solid threaded SBCL on Windows! It’s still a third-party fork for now, but some committers are working on merging it with the mainline.
Fast Constant Integer Division has some stuff on integer division by constants. It’s mostly superseded by Lutz Euler’s work to implement the same algorithm as GCC. There are some interesting identities that can be used to improve on that algorithm a tiny bit and, more interestingly, to implement truncated multiplication by arbitrary fractions. I only stumbled upon those a long time after I wrote the post; I’ll try and come back to this topic in the coming months.
There’s more to locality than caches tracks my attempts to understand why a data structure designed to be cache-efficient did not perform as well as expected. It turns out that cache lines aren’t exactly read atomically (so reading two adjacent addresses may be significantly slower than only one), and that sometimes L2 matters less than TLB. The latter point was an important lesson for me. TLBs are used to accelerate the translation of virtual addresses to physical; every memory access must be translated. TLBs are usually fully associative (behave like content-addressed memory or hash tables, basically), but with a small fixed size, on the order of 512 pages for the slower level. With normal (on x86oids) 4KB pages, that’s only enough for 2 MB of data! Even worse: a cache miss results in a single access to main memory, which is equivalent to ~60-100 cycles at most; a TLB miss, however, results in a lookup in a 4 level page table on x86-64, which often takes on the order of 2-300 cycles. Luckily, there are workarounds, like using 2 MB pages.
Napa-FFT(2) implementation notes is where I try to make the code I wrote for a Fast Fourier transform understandable, especially why it does what it does. Napa-FFT and Napa-FFT2 are vastly faster than Bordeaux-FFT (and than all other CL FFT codes I know, on SBCL), but it’s still around 20-50% slower than the usual benchmark, FFTW. Napa-FFT3 is coming, and it’s a completely different approach which manages to be within a couple percent points of FFTW, and is faster on some operations.
0x7FDE623822FC16E6 : a magic constant for double float reciprocal is a surprisingly popular post. I was trying to approximate reciprocals as fast as possible for a mathematical optimization method. The usual way to do that is to use a hardware-provided approximation and then improve it with a couple iterations of Newton’s method. The post shows how we can instead use the way floats are laid out in memory to provide a surprisingly accurate guess with an integer subtraction. I actually think the interesting part was that it made for a practical use case for the golden section search…
Some notes on Warren has a couple notes about stuff in Warren’s book Hacker’s Delight. The sign extension bit probably deserves more attention; it seems like someone on #lisp asks how they can sign-extend unsigned integers at least once a month.
Two variations on old themes has some stuff on Linux’s ticket spinaphores, and is the beginning of my looking into Robin Hood hashing with linear probing for cache-friendly hash tables.
Interlude: Numerical experiments in hashing covers a first stab at designing a hash table that exploits cache memory. 2-left hashing looks interesting, but its performance was worse than expected, for various reasons, mostly related to the fact that caches can be surprisingly complicated. Two years later, More numerical experiments in hashing: a conclusion revisits the question, and settles on Robin Hood hashing with linear probing. It’s a tiny tweak to normal open addressing (insertions can bump previously-inserted items farther from their insertion point), but it suffices to greatly improve the worst and average probing length, while preserving the nice practical characteristics of linear probing. I’ve also started some work on implementing SBCL’s hash table this way, but there are practical issues with weak hash functions, GC and mutations.
In Specify absolute deadlines, not relative timeouts and the sequel, I argue that we should have interfaces that allow users to specify an absolute timeout, with respect to a monotonic clock. Timeouts are convenient, but don’t compose well: how do we implement a timeout version of an operation that sequences two calls to functions that only offer timeouts as well? Any solution will be full of race conditions. PHK disagrees; I’m not sure if all of his complaints can be addressed by using a monotonic clock.
Finally, Space-complexity of SSA in practices has some early thoughts on how Static single assignment scales for typical functional programs. It’s fairly clear that many compilers for functional languages have inefficient (wrt to compiler performance) internal representations; however, it’s not as clear that the industry standard, SSA, would fare much better.
If you're interested in Lisp, audio/music hacking, just intonation, or microtonality, then this is the sort of thing you're interested in.
First, we'll need a way to play audio. In the past, I dumped the raw audio out to a file in /tmp and played it by shelling out to SoX. These days I can just play it out of an array in memory using my Mixalot library (of which this code requires the latest version, having only recently added support for playback of floating point vectors).
Audio playback through Mixalot is straightforward:
(defparameter *mixer* (mixalot:create-mixer))
(defgeneric play (this))
(defun normalize (vector)
(let ((rescale (/ (reduce #'max vector :key #'abs :initial-value 0.0d0))))
(map-into vector (lambda (x) (* x rescale 0.8d0)) vector)))
(defmethod play ((this vector))
(mixalot:mixer-add-streamer
*mixer*
(mixalot:make-vector-streamer-mono-double-float
(normalize this))))
Next, synthesis - we'll need some audio to play. I'll define a function of frequency called TONE that produces a buffer of audio:
(defparameter *len* 1 "Note length")
(deftype buffer () '(simple-array double-float 1))
(defun make-buffer (size) (make-array size :element-type 'double-float :adjustable nil :fill-pointer nil))
;;; Generate an audible tone.
(defun tone (freq &key; (duration 40000) (len *len*))
(declare (optimize (speed 3)))
(loop with nsamples = (round (* duration len))
with output = (the buffer (make-buffer nsamples))
with decay-rate = (expt 0.07 1/65000)
with omega = (float (/ (* freq 2.0d0 pi) 44100.0d0) 0.0d0)
for amp of-type double-float = 1.0d0 then (* amp decay-rate)
for phase of-type double-float = 0.0d0 then (+ phase omega)
for n from 0 below nsamples
do (setf (aref output n)
(* amp
;; A simple FM (PM) oscillator. Tweaking the magic
;; numbers produces a variety of mostly chime-like
;; timbres.
(sin (+ phase (* 1.5 (expt amp 2) (sin (* phase 5)))))))
finally (return output)))
Next I'll define a simple language for constructing musical phrases from the output of this function. There are two fundamental building blocks:
1. Sequencing of events serially in time:
(defun seq (&rest; args) (apply #'concatenate 'buffer args))
..of which repetition is a special case:
(defun repeat (n &rest; args) (apply #'seq (mapcan #'copy-list (loop repeat n collect args))))
2. Events in parallel:
(defun para (&rest; args)
(reduce (lambda (out in) (declare (type buffer out in)) (map-into out #'+ out in))
args :initial-value (make-buffer (reduce #'max args :key #'length))))
Originally I wrote a simpler definition for this:
(defun para (&rest; args) (apply #'map '(simple-array double-float 1) #'+ args))
This defintion has the disadvantage of truncating the output length to
that of the shortest component. Also, using SBCL, it's much
slower. (apply #'map ...) is a hairy expression that defied
optimization by the compiler, whereas it can inline the map-into
operation. Combined with the type declaration, it expands to a
nice fast addition loop.
Here's some syntactic sugar to make the pieces fit together more nicely, and print some useful information to the REPL:
(defmacro chord (properties &body; body) `(let ,properties (print :chord) (para ,@body)))
(defparameter *tonic* 261.0d0) ; Middle C.
(defmacro just (numerators denominators &rest; args)
`(progn
;; It's useful to see the both reduced fraction and its decimal representation:
(print (list ',numerators ',denominators
(* ,@numerators (/ 1 ,@denominators))
(float (* ,@numerators (/ 1 ,@denominators)))))
(tone (* *tonic* ,@numerators (/ 1 ,@denominators)) ,@args)))
Now, to combine these tools to some musical end. First test: start with a major chord, invert the intervals each way we can, then resolve down to an inversion of the original chord. Note the two different senses of "invert".
(play
(seq
(chord ()
(just (1) (1)) ; Root
(just (5) (4)) ; Major 3rd - 5:4
(just (3) (2))) ; Fifth - 3:2
(chord ()
(just (1) (1))
(just (4) (5)) ; 5:4 becomes 4:5 - The major 3rd is reflected about the octave.
(just (3) (2)))
(chord ()
(just (1) (1))
(just (5) (4))
(just (2) (3))) ; This time, the fifth.
(chord ()
(just (1) (1))
(just (4) (5)) ; - Now both are reflected.
(just (2) (3))) ; -
(chord () ; Resolve down..
(just (1) (1))
(just (5) (4 2))
(just (3) (2 2)))))
Here's a pair of chords used by Harry Partch (see http://en.wikipedia.org/wiki/Tonality_flux):
(let ((*tonic* 196.0)
(*len* 3))
(play
(repeat 2
(seq
(chord ()
(just (8) (7))
(just (10) (7))
(just (12) (7)))
(chord ()
(just (7) (6))
(just (7) (5))
(just (7) (4)))))))
See what he did there? The major chord, built starting 8/7 above the tonic, is reflected around the octave. I find it clearer to consider the minor chord first, built upon an interval of 7/6 above the tonic, then interpret the major as as built symmetrically downward from the next octave.
It's clearer after rewriting the fractions without simplification to separate the 8:7 and octave components from the intervals of the chord:
(let ((*tonic* 196.0)
(*len* 3))
(play
(repeat 2
(seq
(chord ()
(just (8 1) (1 7)) ; 8/7
(just (8 5) (4 7)) ; 10/7
(just (8 3) (2 7))) ; 12/7
(chord ()
(just (2 7 1) (1 8 1)) ; 7/4, i.e. (2*7*1)/(1*8*1), if it isn't clear.
(just (2 7 4) (5 8 1)) ; 7/5
(just (2 7 2) (3 8 1))))))) ; 7/6
Applying that construction under equal temperament, the middle notes of the chords would be the same. Since this is just-intoned, the frequencies differ by about a third of a semitone, and we find ourselves in the uncanny valley of microtonal voice leading.
I've never developed an ear for microtonal music, so I'll run with the idea of this pivoting around the octave and try something conventional. Take the same two chords, add a minor chord on the tonic (without the 8/7 offset) before, and its reflection around the next octave after. Then tack on a little ending to make a more satisfying musical snippet: move down by a fourth, then back, with a couple added notes for color.
(let ((*tonic* 196.0)
(*len* 2))
(play
(seq
(chord () ; 1
(just (1) (1))
(just (6) (5))
(just (3) (2)))
(chord () ; 2
(just (8 ) ( 7))
(just (8 5) (4 7))
(just (8 3) (2 7)))
(chord () ; 3: Built downward from next octave, symmetric with 2.
(just (2 7 2) (3 8))
(just (2 7 4) (5 8))
(just (2 7 1) (1 8)))
(chord () ; 4: Built downward from next octave, symmetric with 1.
(just (2 ) (1))
(just (2 5) (6))
(just (2 2) (3)))
;; Build and release tension.
(chord ((*len* 3))
(just (2 3 ) ( 4))
(just (2 3 4) (3 4))
(just (2 3 3) (2 4)))
(chord ((*len* 1))
(just (2 3 ) ( 4))
(just (2 3 4) ( 3 4))
(just (2 3 3) ( 2 4))
(just (2 3 4 4) (3 3 4))) ; 4/3*4/3, let's call it a dominant seventh.
(chord ((*len* 4))
(just (1) (1))
(just (6) (5))
(just (3) (2))
(just (2) (1))
(just (3) (1)))))) ; ..and that's a ninth. The 7th resolved here.
Stacking fourths:
(let ((*tonic* 262.0)
(*len* 2))
(play
(seq
(chord ()
(just (1) (1))
(just (4) (3)))
(chord ()
(just ( ) ( )) ; Did I mention the ones are optional?
(just (4 ) ( 3))
(just (4 4) (3 3)))
(chord ()
(just ( ) ( ))
(just (4 ) ( 3))
(just (4 4 ) ( 3 3))
(just (4 4 4) (3 3 3)))
(chord ()
(just ( ) ( ))
(just (4 ) ( 3))
(just (4 4 ) ( 3 3))
(just (4 4 4 ) ( 3 3 3))
(just (4 4 4 4) (3 3 3 3)))
(chord ()
(just ( ) ( ))
(just (4 ) ( 3))
(just (4 4 ) ( 3 3))
(just (4 4 4 ) ( 3 3 3))
(just (4 4 4 4 ) ( 3 3 3 3))
(just (4 4 4 4 4) (3 3 3 3 3))))))
Stacking fifths and fourths:
(let ((*tonic* 262.0)
(*len* 1))
(play
(seq
(chord ()
(just ( ) ( ))
(just (3 ) ( 2))
(just (3 4) (3 2)))
(chord ()
(just ( ) ( ))
(just (4 ) ( 3))
(just (4 4) (3 3)))
(chord ((*len* 2))
(just ( ) ( ))
(just (3 ) ( 2))
(just (3 3 ) ( 4 2))
(just (3 3 4) (3 4 2)))
(chord ()
(just ( ) ( ))
(just (4 ) ( 3))
(just (4 4 ) ( 3 3))
(just (4 4 4) (3 3 3)))
(chord ()
(just ( ) ( ))
(just (3 ) ( 2))
(just (3 4 ) ( 3 2))
(just (3 4 4) (3 3 2)))
(chord ((*len* 4))
(just ( ) ( ))
(just (4 ) ( 3))
(just (4 3 ) ( 2 3))
(just (4 3 4) (3 2 3))))))
Various ways to express the a major triad:
(play
(seq
(chord () ; Three separate notes.
(just (1) (1))
(just (5) (4))
(just (3) (2)))
(chord ()
(just (1 ) ( 1))
(just (1 5 ) ( 4 1)) ; A major 3rd..
(just (1 5 6) (5 4 1))) ; ..and a minor 3rd on top of that.
(chord ()
(just (1 ) ( 1))
(just (1 3 ) ( 2 1)) ; Or, you could nest the third inside
(just (1 3 5) (6 2 1))))) ; the fifth by a downward interval.
The Tristan Chord (in equal temperament)
(play
(chord ((*tonic* 349.0) ; F
(*len* 3))
(tone (* *tonic* (print (expt 2.0 0/12))))
(tone (* *tonic* (print (expt 2.0 6/12))))
(tone (* *tonic* (print (expt 2.0 10/12))))
(tone (* *tonic* (print (expt 2.0 15/12))))))
Assume we're in A minor, and the chord is inverted such that the root is B. The tritone between B and F is problematic. It's an unnatural interval in just-intonation, and there are various ways to interpret it relative to the other notes.
(play
(chord ((*tonic* 440.0)
(*len* 3))
(just (9 ) ( 8)) ; B
(just (9 5 ) ( 4 8)) ; B * 5/4 = D#
(just (9 5 4) (3 4 8)) ; D * 4/3 = G#
;; Now we need that F. Three ways to get there spring to mind:
;; 1. Two intervals of 3/4 downward from D#. Yields intervals of
;; 45/64, 9/16, and 27/64 versus the other notes in the chord, and
;; an ungainly 405/512 versus the tonic. The ratios are ugly, but
;; the sound is quite close.
;; (just (9 5 9) (16 4 8))
;; 2. An intervals of 5/9 downward from D# yields intervals of
;; 25/36, 5/9, and 5/12 versus the rest of the chord, and 25/32
;; against the tonic. The pitch ratios within the chord are mostly
;; nice and simple, but the F sounds oddly flat.
;; (just (9 5 5) (9 4 8))
;; 3. An interval of 7/10 downward from the root, yielding
;; intervals of 7/10, 14/25, and 21/50 versus the rest of the
;; chord, and 63/80 against the tonic. Constructing the F relative
;; to the root of the chord seems preferable, even if it introduces
;; a new factor of 7 into the ratios, which make the intervals
;; against D# and G# odd. Overall, I prefer the sound of this
;; one. The F is slightly flat compared to the equal-tempered
;; chord, but not unpleasantly so.
(just (9 7) (10 8))))
When you uncomment more than one of the above versions of 'F', they're slightly detuned and beat against each other. This could be a cool compositional device to highlight shifts in the tonality. I'll try it with the two Partch chords:
(let ((*tonic* 196.0)
(*len* 3))
(play
(repeat 2
(seq
(chord ()
(just (8 1) (1 7))
(just (8 5) (4 7))
(just (8 3) (2 7)))
(chord ()
(just (8 1) (1 7))
(just (8 5) (4 7))
(just (2 7 4) (5 8 1)) ; Presages the following chord..
(just (8 3) (2 7)))
(chord ()
(just (2 7 1) (1 8 1))
(just (2 7 4) (5 8 1))
(just (2 7 2) (3 8 1)))
(chord ()
(just (2 7 1) (1 8 1))
(just (2 7 4) (5 8 1))
(just (8 5) (4 7)) ; And again..
(just (2 7 2) (3 8 1)))))))
That suggests a more subtle trick. Rather than playing both tones to mark the shift, replace one with the similar tone from the next chord.
(let ((*tonic* 196.0)
(*len* 3))
(play
(repeat 2
(seq
(chord ()
(just (8 1) (1 7))
(just (8 5) (4 7))
(just (8 3) (2 7)))
(chord ()
(just (8 1) (1 7))
(just (2 7 4) (5 8 1)) ; Replaced (8 5) (4 7) to lead into the next chord.
(just (8 3) (2 7)))
(chord ()
(just (2 7 1) (1 8 1))
(just (2 7 4) (5 8 1))
(just (2 7 2) (3 8 1)))
(chord ()
(just (2 7 1) (1 8 1))
(just (8 5) (4 7)) ; Likewise, replaced (2 7 4) (5 8 1)
(just (2 7 2) (3 8 1)))))))
That's all for now. I've published the code on Github here.
Incidentally, I've concluded my world tour (my Thailand visa having expired, and an additional season of leisure and international intrigue being financially unwise), so if you're looking for a versatile young Lisp/C/C++ hacker with a background in computer/network security and an equal penchant for spiffy user interfaces and gritty low-level trawling around in debuggers and disassemblers, you could do worse than to get in touch.
You are kindly invited to the next Berlin Lispers Meetup, an informal gathering for anyone interested in Common Lisp and other languages in the Lisp family.
Berlin Lispers MeetupThere will be two presentations:
Please join for another evening of parentheses!
Twitter: @BerlinLispers
;;; This file defines the base flavors for compiler objects and their protocols.
;;;
;;; The function of a compiler object is fairly simple: it must translate some
;;; program source into an appropriate object representation, and put that representation
;;; somewhere.
;;;
;;; A compiler object is made up of several components, which may be inter-related:
;;; - The Language Parser, which differentiates between various dialects of Lisp
;;; - The Target Architecture on which the object representation should run
;;; - The Compilation Environment
;;; - The Intermediate-Representation Optimizer
;;; - The Compilation Target, which actually disposes of the object representation
;;; (e.g. load it into virtual memory, or store it in a file)
;;;
;;; A Language Parser is responsible for all of the steps required to translate
;;; the source into an intermediate representation, including all source-to-source
;;; and source-to-pseudo-source transformations. [By pseudo-source we mean constructs
;;; which are similar in syntax to those of the source language, but which may not be
;;; understood correctly by an interpreter of the source language - "compiler-only forms",
;;; to put it another way.] The Language Parser may make use of the Target Architecture,
;;; and will certainly make use of the Compilation Environment.
;;;
;;; The task of the Target Architecture is to translate the program from its intermediate
;;; representation into an object representation that corresponds to the Instruction-Set
;;; Architecture of the machine on which the target is to be run. [Note that a "machine"
;;; can be a software emulation as well as a physical computer.] As previously stated, the
;;; Target Architecture may also be used by the Language Parser in the source-to-intermediate
;;; translation step. The Target Architecture may also interact with the
;;; Intermediate-Representation Optimizer, as described below.
;;;
;;; The Compilation Environment contains an evaluator and definitions that are needed at
;;; compile-time. Its main function is to support macroexpansion and compile-time
;;; error-checking.
;;;
;;; The Intermediate-Representation Optimizer does just what its name implies. It bases its
;;; decisions on advise from the user (e.g. the CL OPTIMIZE declaration). It may interact
;;; with the Target Architecture when the optimization is more easily implemented or disabled
;;; at this level, rather than during the intermediate-to-object translation.
;;;
;;; The Compilation Target's reponsibility is to put the object representation somewhere.
;;; This could either be the local Lisp virtual memory, a remote Lisp, either running on
;;; another piece of hardware or in a software emulation, or a file of some kind.For lack of anything more interesting to say I will tell you about something that for some reason remained not entirely obvious to me until very recently.
When using ZMQ_PUB/ZMQ_SUB sockets and ZMQ_SUBSCRIBE message filters on one side of the interaction it is possible to separate the sending of messages from the sending of subscription channel information using multipart messages.
While working on Clubot with Sean Bryant earlier it became frustrating to consume the output of the bot since the messages coming out were coming out as single strings of JSON prefixed with a series of subscription symbols and strings as in:
:PRIVMSG :CHATTER "#somechan" "Origin_nick" {..[json]..}
So to consume the message symbols and strings have to be consumed from the beginning of the string until we can start to read JSON from an offset. This was no good.
ZeroMQ promises in the documentation of zmq_send that multipart messages will be delivered either as a complete sequences or not at all. Following this logic, we should be able to send subscription information up front in a single frame then follow up the body in a second and still have the full benefits of filtering using ZMQ_SUBSCRIBE with the added benefit of being able to slurp in the entire second message on the assumption that it contains JSON data.
So putting it all together. The publisher creates a message body and subscription information as two strings. It then sends the message as a pair of multipart messages with the subscription information first as in:
(zmq:send *pub-sock* header-message ZMQ:SNDMORE)
The ZMQ_SNDMORE flag indicates that this message will be followed by additional parts and should not be consumed individually.
The body is then written as usual:
(zmq:send *pub-sock* body-message)
The message will be published to any interested peers and the the pair of messages will be filtered by the contents of the first message with the body just tagging along.
To read a multipart message stream correctly we have to use the ZMQ_RCVMORE socket option to check for additional messages in the message stream. We can skip this step if our protocol is precise and we have foreknowledge of the number of message parts arriving, but doing so would leave a potential problem in the future.
The ZMQ_RCVMORE option when queried with zmq_getsockopt from a socket will return either 0 or 1 for either completing the multipart read (0) or having additional data available (1) as message components that were delivered with ZMQ_SNDMORE.
So we can receive the orignal example message when sent as a two-part multipart message following something like the following:
;; Still filter on the first part of the entire sequence
(zmq:subscribe *sub-socket* ":PRIVMSG")
(zmq:recv *sub-socket* subscription-header)
Now subscription-header will contain a string containing just the message prefix. If we use the same message it would be :PRIVMSG :CHATTER "#somechan" "Origin_nick"
We can then check to make sure that there is additional data to read and read the data component of the message or signal an error.
(if (= (zmq:getsockopt *sub-socket* zmq:rcvmore) 1)
(zmq:recv *sub-socket* data-message)
(error "No message followed subscription header!"))
If that form evaluates without error then data-message should contain just the data JSON without any more required to separate it! Isn't that exciting?
Happy cooking.
Last week I was doing some cleanup work (short holiday weeks are great for paying off technical debt), and was deleting some supposedly unused code. This was a pretty tedious process of running functions like slime-who-calls and slime-who-references, running git grep -i on the command line, and undefining functions in just the right order.
I’ve seen a lot of articles recently on static analysis of code, and spent some time playing with the introspection features of slime to identify unused code (short holiday weeks are also great for following a tangents). I ended up with a slow mudball of code that worked pretty well.
Warning, large images coming up.
The code itself is up on github, but there’s no ASDF system yet, so you have to load it manually:
(require :iterate) (require :alexandria) (require :swank) (load "~/lisp/static-analysis/static-analysis.lisp") (in-package :static-analysis)
An truncated example:
STATIC-ANALYSIS> (call-graph->dot :alexandria )
digraph g{
subgraph clusterG982{
label="PACKAGE STATIC-ANALYSIS"
G983 [label="ENSURE-PACKAGE-LIST"]
}
subgraph clusterG949{
label="PACKAGE ALEXANDRIA.0.DEV"
...
}
G983 -> G995
...
G951 -> G950
}
NIL
Here’s what it actually looks like:
The code currently scans all loaded code, and puts functions from each package in it’s own graphviz subgraph. The graph for an entire package for all loaded code isn’t really that useful, so I made another function to narrow it down. Here I’m specifying the list of packages to render, and the list of functions to show.
STATIC-ANALYSIS> (->dot (function-call-graph '(:alexandria) '(alexandria:rotate)))
digraph g{
subgraph clusterG1109{
label="PACKAGE ALEXANDRIA.0.DEV"
G1040 [label="ROTATE-HEAD-TO-TAIL"]
G1049 [label="SAFE-ENDP"]
G1054 [label="CIRCULAR-LIST-ERROR"]
G1051 [label="PROPER-LIST-LENGTH"]
G1042 [label="ROTATE-TAIL-TO-HEAD"]
G1041 [label="ROTATE"]
}
G1040 -> G1051
G1051 -> G1049
G1051 -> G1054
G1042 -> G1051
G1041 -> G1040
G1041 -> G1042
}
NIL
Some systems have very complicated call graphs. At work we do a lot with clsql, and the overall call graph even from one function can get complicated quick:
So I added a depth param to keep the graph smaller, let’s say 3:
STATIC-ANALYSIS> (->dot
(function-call-graph '(:clsql-sys :clsql-sqlite3)
'(clsql:map-query)
2))
Anyhoo, a fun toy, and I had a fun time writing it.
In the REPL, +, ++, and +++ have as values the three most recently evaluated expressions. A quick way to evaluate the previous expression, especially handy in a REPL without input history, is
#.+
It's equivalent to (eval +).
(Thanks to Anton Kovalenko.)
When writing applications based on SBCL for work or leisure I often find myself needing the final product to be an executable lisp core. SBCL has built-in support for this with save-lisp-and-die and external support through Buildapp and they work well for most cases.
The issue that I continuously ran head-first into was the behavior of the lisp image with regard to opening any shared objects at runtime after a restart. save-lisp-and-die has the option to disable or enable the reloading of these objects, but when it is enabled the object it will attempt to load will be searched for by an absolute path. So an application built with a library at /opt/local/lib/foo.so will fail on a system where the library exists at /usr/local/lib/foo.so even if the dynamic linker is configured to find the library foo.so at both paths.
To solve this problem we must first find the code responsible for reloading the objects in the first place and it is: SB-ALIEN::TRY-REOPEN-SHARED-OBJECT
As a parameter it takes an object that it should attempt to reload. If we're reloading a core on a machine it was not compiled on the paths of these objects will be absolute and absolutely incorrect. What can be done is the function can be intercepted and the path stripped from the absolute name of the object to just the last component. This will signal the dynamic linker to search for the named library in accordance with its configuration. Obviously, since this happens at run time, if the object can be found at the exact path it should be favored over whatever would be found if left up to the dynamic linker.
The code to accomplish something like this would look almost exactly like this:
[Gist: https://gist.github.com/1549389]
(with-unlocked-packages (:sb-alien)
(let ((function (symbol-function 'sb-alien::try-reopen-shared-object)))
(setf (symbol-function 'sb-alien::try-reopen-shared-object)
#'(lambda (obj)
(declare (type sb-alien::shared-object obj))
(let ((path (sb-alien::shared-object-pathname obj)))
(when (pathname-directory path)
(unless (probe-file path)
(let ((sub-path (make-pathname :name (pathname-name path)
:type (pathname-type path))))
(setf (sb-alien::shared-object-pathname obj)
sub-path
(sb-alien::shared-object-namestring obj)
(namestring sub-path))))))
(funcall function obj)))))
This code can sit in a helper outside of the dependency chain configured by ASDF and be loaded into the image only when producing a core either programmatically or with an explicit —load or —eval during the build step. As long as the patch ends up in the resulting image that specific class of headaches should go away.
Here's a function to get the underlying array on which a displaced array is based:
(defun undisplace-array (array)
"Return the fundamental array and the start and end positions into
it of a displaced array."
(let ((length (length array))
(start 0))
(loop
(multiple-value-bind (to offset) (array-displacement array)
(if to
(setq array to
start (+ start offset))
(return (values array start (+ start length))))))))
From an article by Erik Naggum.
It would appear events aren't being signalled correctly on a DEALER->ROUTER (at least? maybe? It's the only evidence I have) communication in in the IOLib multiplexer when the FD bound for read is one discovered from a ZeroMQ socket with ZMQ_FD then checked against ZMQ_EVENTS.
If I close the socket and terminate the context after the request the message is always delivered, much like a message is pulled from the queue when another client connects and disconnects. Events on the plain file descriptor tied to a network socket seem to fire just fine, though.
I'll have to run some science later using the native zmq:poll to make sure they are supposed to arrive like I expect, but if they don't I might attempt to bring a bridge over to libev and never have this problem again for as long as I parenthesize.
https://github.com/HackingGibsons/clubot
I started working on a project to bridge IRC to IRC-based services using ZeroMQ as a transport. So far I've managed to convince it to connect and broadcast out messages that are easy to ZMQ_SUBSCRIBE to and handle a few commands sent as JSON over another set of 0MQ sockets.
Hopefully it ends up more useful than just fun.
Sean Bryant started hacking together an adapter to link in Hubot as a 0MQ service called Hubot-Shaky this should make this project useful more quickly.
The project runs on SBCL and is still in the early stages. The only way to operate it is still from a REPL. I'm posting it now because it's at a point where it's theoretically possible to write something like a Google service, and in case any one has every been curious about digging through a small-scale but sanely packaged Common Lisp project.
Matthew Snyder has a great introductory post on his blog where he converts the Mustache spec into a runnable fiveam test suite. Very cool stuff - I hope he posts more (-:
In defmethod lambda lists, required parameters that aren't explicitly specialized default to specializing on the system class t. But there's a difference between an implicit and explicit specialization on t. The hyperspec explains:
The expansion of the defmethod macro "refers to" each specialized parameter (see the description of ignore within the description of declare). This includes parameters that have an explicit parameter specializer name of t. This means that a compiler warning does not occur if the body of the method does not refer to a specialized parameter, while a warning might occur if the body of the method does not refer to an unspecialized parameter. For this reason, a parameter that specializes on t is not quite synonymous with an unspecialized parameter in this context.
This makes a difference in Clozure CL; here's what you get if you don't use an implicitly specialized required parameter:
? (defmethod foo (bar) 42) ;Compiler warnings : ; In (FOO (T)) inside an anonymous lambda form: Unused lexical variable BAR #
There is no warning with an explicit specialization:
? (defmethod bar ((baz t)) 42) #
(Thanks to Hans Hübner.)
Logically speaking, POSITION with trivial :KEY and :TEST arguments should be much faster on bit-vectors than on simple vectors: the system should be able to pull one words worth of bits out of the vector at a single go, check if any are set (or unset), and if so locate the one we're interested in -- else going on to grab the next word.
Practically speaking, no-one who needed fast POSITION on bit-vectors seems to have cared enough to implement it, and so until yesterday (1.0.54.101) SBCL painstakingly pulled things one bit at a time from the vector, creating a lot of unnecessary memory traffic and branches.
How much of a difference does this make? I think the technical term is "quite a bit of a difference." See here for the benchmark results. First chart is from the new implementation, second from the new one. Other calls to POSITION are included for comparison: ones prefixed with generic- all go through the full generic POSITION, while the others know the type of the sequence at the call-site, and are able to sidestep a few things.
So, if you at some point considered using bit-vectors, but decided against them because POSITION wasn't up to snuff, now might be a good time to revisit that decision.
Gory details at the end of src/code/bit-bash.lisp, full story (including how the system dispatches to the specialized version) best read from git.
Also, if you're looking for an SBCL project for next year, consider the following:
Happy Hacking and New Year!
A common complaint from a co-worker is not being able to find relevant library functionality. We have libraries that do some tasks well, but if you haven't used it before, how are you to know that it is there. More over, how do you find what you are looking for from all of the available utility libraries currently loaded.
After seeing Peter Seibel's Manifest screencast. I was struck by the idea that you could index all the doc strings to provide a powerful search tool. I dont know about powerful yet, but this idea has turned into at least a search tool: Manifest-Search. This is the product of one days hacking and so should not be construed as the end-all-be-all common lisp search tool, however, it is at least a step in that direction.
I would like to eventually get this integrated more fully with both quicklisp and manifest, but that is all in the future. I think it would be amazing to search for functionality I need, and get documentation for a library I have not yet installed, but is distributed by quicklisp.
I’ve spent the last few hours dusting off my Common Lisp Growl client library. The last time I worked on it was before the Mac Growl Application supported GNTP (Growl Notification Transport Protocol).
Today, working on it, I’m not quite sure what’s up, but I am not succeeding in communicating with the server using encryption. I’ll have to look more closely. Last time that I worked on it, I extended Ironclad, but I never got those changes pushed fully into Ironclad’s main line. But, I think I’m using the same version of Ironclad that I was using when I tested against the Windows Growl Application. *shrug*
I’ve also run into a snag with the Callbacks. Essentially, your Lisp program could get a callback when the user has clicked on your Growl notification. This actually works except for the fact that I am calling READ-SEQUENCE into a buffer that is longer than the message. The server, I believe, is supposed to close the socket after the callback. But, it does not. So, I am stuck waiting for more bytes that will never come.
Now, I either have to do one of the following:
READ-LINE instead:dont-wait works as expected)SOCKET-RECEIVE even on TCP socketsAnyone have a preference?
My IDNA library now supports decoding IDNA strings via the to-unicode function:
(to-unicode "xn-mller-kva.example.com")
;; => "müller.example.com"
That’s in addition to the regular encoding for unicode domain names:
(to-ascii "müller.example.com")
;; => "xn-mller-kva.example.com"
Sadly, I haven’t managed to get the logic for case-sensitive punycode encoding to work yet. But fortunately, IDNA domain name encoding doesn’t require that! Anyone looking for some low-hanging fruit-shaped lisp projects is welcome to add that! (-:
Everyone writes new methods for print-object for specialized printing of their own objects. But the output of describe can be specialized as well via describe-object. For example, if you have an object that has a vector of keys and a corresponding vector of values, the standard describe output might not show them in a way that's very readable:
* (describe record)
#
[standard-object]
Slots with :INSTANCE allocation:
SOURCE = #P"data.txt"
ID = 47
KEYS = #("Rating" "Unit Price" "Description" "Acquired Date")
VALS = #(7.2 21 "Blue-fringed tarpaulin with star pattern"..
You can add your own primary or auxiliary methods to change what describe prints:
(defmethod describe-object :after ((record record) stream)
(write-line "Data values:")
(loop for key across (keys record)
for val across (vals record)
do (format stream " ~A: ~A~%" key val)))
Then the describe output can be more readable:
#
[standard-object]
Slots with :INSTANCE allocation:
SOURCE = #P"data.txt"
ID = 47
KEYS = #("Rating" "Unit Price" "Description" "Acquired Date")
VALS = #(7.2 21 "Blue-fringed tarpaulin with star pattern"..
Data values:
Rating: 7.2
Unit Price: 21
Description: Blue-fringed tarpaulin with star pattern
Acquired Date: 3532942160
As my learning resource, I chose The Ruby Programming language by David Flanagan and Yukihiro Matsumoto as that receives great customer reviews, covers Ruby 1.8.7 and 1.9 and is authoritative because the language creator is one of the authors.
The book makes a good read in general. There are plenty of code examples, but not too much to obscure the prose. What I found first interesting, later annoying, was the frequent use of words like "complex", "complicated", "confusing", "surprising" or "advanced" to describe features of the language. I'd rather decide myself about using such attributes to describe something that I've just learned.
Having spent so much time with Common Lisp, I almost forgot that programming languages usually evolve over the years. Ruby is no exception, and the fact that there are significant differences between Ruby 1.8.7 and Ruby 1.9 kind of bothers me - I'll probably never write code in Ruby 1.8.7, but the differences between the two versions seem to be rather subtle and I'm curious to see how much that is going to be a bother in the future, working with legacy code.
The common theme for Ruby seems to be succinctness. This comes at the expense of making the syntax rather complex, with several special case rules required to solve ambiguities. I don't have the practice to judge whether this is a problem, but from the book, it seems there are quite some things to remember.
It seems that Ruby started off as a purely object oriented language and only later discovered that function-oriented programming is nice, too. The deep roots of object orientation made it rather hard to actually get free functions (which are not member functions of an object) integrated. Contrary to what I am used to, member functions are not a special case of free functions, but rather something quite different. It requires explicit conversion steps to convert a member function into a free function (called procs in Ruby), and invocation syntax is also different between the two. Again, the description may sound worse than it is in practice.
What I really liked was the generalization of code blocks into fibers. Ruby does not have full coroutines, but the restricted form that is available is generalized well and seems like it could be useful for building pretty wild asynchronous systems. Also, it is nice that the bindings of closures can be accessed.
But then, Ruby is an interpreted language and this fact is re-stated throughout the book. With Just In Time compilation, this could become a non-problem, but I'm not sure how well Ruby can be optimized due to its very dynamic nature. Just to see how fast it is compared to Common Lisp, I implemented the Sudoku solver from chapter 1 of the Ruby book in CL and gave the two implementations a puzzle to solve. It took the Ruby solver 0.890 CPU seconds (Ruby 1.9.2p290), whereas the Lisp solver (Clozure CL 1.7) used 0.087 CPU seconds to solve the puzzle. Ten times slower, whatever you'll make of that.
In the book, it is mentioned how little code the Sudoku solver actually uses. This is true, but then, the Lisp version is not longer. It does not seem as if adding syntax is actually the best way to add the possibility to write succinct programs to a language, and the price of the complex grammar is rather high.
Writing the CL solver, I found myself not writing tests again and then poking around in problems of my implementation without knowing what works and what does not. As I want to practice more TDD, I stepped back and added tests. This led me to solve a problem that I had with my previous attempts to practice TDD in Lisp - I do not want to export all the symbols that the tests exercise from the packages that I use, but I also don't want to import the unit testing library into my own library packages. Thus, I wrote a deftestpackage macro that creates a new package to contain the tests that I write and automatically imports all symbols from the package being tested. That way, I can easily keep tests and library source separate and don't need to qualify internal symbols in the tests.
My overall takeaway on the Ruby is this: Ruby seems to be a language that has grown from being purely object oriented to supporting functional programming. That growth was not completely natural, and it seems that if Ruby is not used as a pure object oriented language, the syntax becomes rather messy and hard to grasp. This is similar to C++, which in its first versions was relatively nice (I hear you "ow"!), but has grown into into an incomprehensible mess once people recognized how templates can be abused for metaprogramming.
I can see the appeal of Ruby, but there seems little it has to offer to me that Common Lisp cannot provide. The lack of a formal specification and the ugly grammar put me off. Then again I'm pretty sure that Ruby is more enjoyable than many other popular languages. I'm looking forward to see my theoretical conceptions be shaken by actual practice.
For older items, see the Planet Lisp Archives.
Last updated: 2012-01-26 09:12