Breaking out of Klipse

November 30, 2017

Every so often, the Clojurians community in London runs a workshop for people under-represented in the software industry, called ClojureBridge (the workshop materials are available online.) It's advertised as targeted for both beginners and experienced programmers, so being the curious sort, I naturally signed up.

During the workshop you play with Clojure in Klipse. One of the early things they teach you is arithmetic: how to sum up a sequence of numbers. Said numbers are listed on the workshop webpage. The student needs to type them into Klipse manually. At that, my brain strongly protested. The numbers, it said, are right there. Why do I need to re-type them? Why not just strip them out of HTML?

Ladies and gentlement, here's how to break out of Klipse sandbox.

The first clue came when the workshop directed us to define a variable in one Klipse window, and then use the variable in another. Which means all Klipse windows share the same namespace, somehow. And Klipse is written in Javascript. Is this shared namespace by any chance the global namespace?

I googled a bit and found an article on how to edit DOM with Klipse. Hello, .-innerHTML and js/document.getElementById sound familiar! Turns out that what you write in Klipse is ClojureScript. And it does have access to the document object. Without further ado: how to strip numbers from the ClojureBridge workshop, reproduced below in case the curriculum changes.

What is the total number of years from all the following languages

  • Clojure (10 years)
  • Haskell (27 years)
  • Python (26 years)
  • Javascript (21 years)
  • Java (22 years)
  • Ruby (22 years)
  • C (45 years)
  • C++ (34 years)
  • Lisp (59 years)
  • Fortran (60 years)
; elements dependent on document structure isolated into their own fn
(defn scrape-languages []
    ; get first panel on the page
    (.getElementsByClassName "panel-body")
    (aget 0)
    ; get the UL within the panel
    (aget 1)
    ; get all the LIs
    ; scrape the language + age strings
    ((fn [lis] (map #(.-innerHTML %) lis)))))

(defn extract-language [s]
    ; the number is language age
    :age (js/parseInt (re-find #"[0-9]+" s))
    ; the first word is language name
    :name (re-find #"^\S+" s)))

(def languages
  (map extract-language (scrape-languages)))

(reduce + 0 (map :age languages))

Extra info for people like myself who had no previous exposure to ClojureScript:

  • js/ is how you drop down to Javascript from ClojureScript
  • .- is how you access Javascript object properties
  • aget! is how you access Javascript collection elements
  • (and set! is how you set Javascript object properties)

You can then use languages as defined above to solve the other questions from the workshop:

Average age of programming languages?

(let [ages (map :age languages)]
  (/ (reduce + 0 ages) (count ages)))

Find the age of the youngest programming language:

(apply min-key :age languages)
Tags: clojure hack