Wishful Coding

Didn't you ever wish your
computer understood you?

My Bookshelf 4/5: Mining the Social Web

This book seems almost written for me. It starts of with my favourite platform: Twitter. Next it covers micro formats, which I was just getting interested in. And finally, working with similarity and clustering released a slew of new ideas.

Mining the Social Web cover

Wht is interesting to note is that this book references a lot of material. This means that it contains more good stuff than fits between the covers, but also that some of the stuff you want to know cannot be found between the covers.

All code used in the book (and more) can be found on github, which saves a lot of typing when you want to play around. The downside is that some longer examples and utilities are only on github, which is sub-ideal when you are reading away from the internet or the computer.

The code used in the book is written in Python, arguably because of its readable syntax and library support, especially the NLTK.

The book also uses Redis and CouchDB extensively, which is not so easily justified at this small scale in my opinion. Later in the book, Pickle is used most of the time.

This book covers a lot of ground in this broad subject, and gives you the tools to explore subjects in-depth yourself. Definitely recommended reading.

Can't take value of a macro

Do. not. ever. make me see that exception. Because in my opinion that means you failed at designing your shiny DSL.

A macro is not a thing, but a transform from one thing to another. If your DSL does not have things – domain specific values, as Christophe Grand calls them – at its core, you’re doing it wrong.

First and foremost, a macro should be simple, where simple means ‘not compound’, as explained by Stuart Halloway.

I would like to define a simple macro as one that is written as a compound of simple functions returning code that evaluates to a simple or composite value.

  • (+ 1 1) returns a simple value
  • (a-record. 1 2 3) evaluates to a composite of simple things
  • (do (def foo 2) (def bar 4) (do-thing-to foo)) is not simple

Another good non-reason to use macros is to defer execution.

(defmacro assoc-once [m k v]
  `(if-not (get ~m ~k)
     (assoc ~m ~k ~v)
     ~m))

(assoc-once {:a 1} :a (println "foo"))
; {:a 1}
(assoc-once {:a 1} :b (println "foo"))
; foo
; {:b nil, :a 1}

Works nicely, uh? Now assume we have an atom, try (swap! a assoc-once ...) It’ll honor our post title: Can’t take value of a macro

You know what else defers execution? A function.

(defn assoc-once [m k v]
  (if-not (get m k)
    (assoc m k (v)) ; note parens
    m))

(assoc-once {:a 1} :a #(println "foo"))
; {:a 1}
(assoc-once {:a 1} :b #(println "foo"))
; foo
; {:b nil, :a 1}

This is of course not as good-looking as the macro, but the swap! case works fine here. If you care enough, you could define a macro on top of the function, giving you a simple macro.

(defmacro assoc-once [m k v]
  `(assoc-once* ~m ~k (fn [] ~v)))

Now, this is a very long and opinionated post for me already, so I’m going to stop here. I highly recommend that you watch the 2 video’s I linked to.

I had in mind to take you through another example of using protocols and function composition to simplify and avoid macros, but I’ll just give you this, this and that as a homework assignment.

Published on

Generative Music

It’s been a long time since I played with Overtone. What I’ve always wanted to do is generate music automatically. Today I ran into jFugue, which makes this really easy, listen to this:

</param> </param> </embed>

Markov generated music by pepijndevos

I just took some music from wikifonia, constructed a Markov-chain and did a random note-walk.

This could work just as well with Overtone, but I used jFugue because it comes with a parser for MusicXML files.

To run the code below, you need to have jFugue on the classpath. Then just pipe a couple of notes from generate into play.

(ns ponger.core
  (:require clojure.string))

(defn inflate-mxl [file]
  (let [z (java.util.zip.ZipFile. file)]
    (slurp
      (.getInputStream z
        (.nextElement (.entries z))))))

(defn parse-musicxml [file]
  (let [parser   (org.jfugue.parsers.MusicXmlParser.)
        renderer (org.jfugue.MusicStringRenderer.)]
    (.addParserListener parser renderer)
    (.parse parser (inflate-mxl file))
    (.getPattern renderer)))

(defn notes [tokens]
  (filter #(re-matches #"[A-GR].*" %) tokens))

(defn voices [tokens]
  (let [[f [_ & r]] (split-with (complement #(.startsWith % "V")) tokens)]
    (when (seq f)
      (cons f (voices r)))))

(defn make-chain [tokens]
  (into {}
    (for [[k slice] (group-by first (partition 2 1 tokens))]
      [k (map last slice)])))

(defn walk-chain [chain start]
  (let [nxt (rand-nth
              (get chain start
                   (apply concat (vals chain))))]
    (cons
      nxt
      (lazy-seq
        (walk-chain chain nxt)))))

(defn generate [path]
  (-> path
    parse-musicxml
    (.getTokens)
    notes
    make-chain
    (walk-chain nil)))

(defn play [token-seq]
  (let [player (org.jfugue.StreamingPlayer.)]
    (doseq [token token-seq]
      (.stream player token))))

(defn save [token-seq path]
  (.saveMidi (org.jfugue.Player.)
         (clojure.string/join " " token-seq)
         (java.io.File. path)))