How to buy a record player

My turntable and records
Beautiful, beautiful music
  1. Fall in love with your grandfather’s customised record player and wonderful speakers when you listen to his records as a young kid.

  2. Accidentally walk into The Adelphi in your first few weeks in Singapore and ogle at all the fancy audio equipment. More than half the shops there look like living rooms – replete with comfy sofas to sit back in and enjoy the music. One of them even has guitars hanging on the walls!

  3. Do a bit of research into the cost of said audio equipment. Step back from your computer screen in horror for the next lifetime or so.

  4. Don’t let that stop you from looking at all record players making appearances all over Singapore – even Robinson’s has them!

  5. Make the final payment of your student loan just before Christmas.

  6. ’Tis the season to be jolly! Maybe I should celebrate by gifting myself something. How about a … record player?!

  7. Look at reviews, visit some record stores, and talk to old uncles in the basement of The Adelphi. Look at more reviews while visiting stores. Compare prices on Amazon and be surprised. Visit more stores and keep teetering on making a decision because no one else you know can provide advice on this esoteric hobby.

  8. Finally buy a beautiful one and bring it home.

  9. Don’t forget to buy some records too! It’s okay if the record store guy convinces you to buy an album that you’d never normally listen to because “it sounds so good!”

  10. Agonise over the poor wiring in your house causing your new equipment to sound noisy.

  11. Try many different ways of debugging and fixing it.

  12. Give up and make peace with it.

  13. Buy some more records. Admire the beautiful artwork. Play them. That’s what it’s all about anyway!

  14. Sit back. Close your eyes.

  15. Listen. Listen!

It’s worth mentioning that steps 8, 13, and 15 are really the most crucial ones. In fact I could go so far as to say that all the other steps can be safely ignored. Most importantly, enjoy the music!

Shell Scripting in ClojureScript with Planck

I’ve been doing a fair bit of shell-scripting recently, mostly of the data munging variety for some of my side projects. I quite enjoy working with command line tools, but dealing with structured data (JSON) isn’t too pleasant. jq is nice but it defines a DSL that I’ve never found intuitive except for simple tasks.

In looking for opportunities to use Clojure for Real Stuff™, I thought – why not try it out for those shell scripts? It has a fantastic standard library specifically for transforming data. The only downside is the interpreter’s notoriously prohibitive startup time (I’m trying to replace shell scripts after all). Then I remembered that I have Planck (a ClojureScript REPL) installed, which is super snappy in comparison, so I decided to give that a shot.

As it turns out, Planck has great support for shell scripting. I’ve been so happy with it that I thought I’d share some of the features that make it really useful. Here goes:

Invoking external shell commands with sh

Planck can easily execute other shell tools and return the results using the sh function.

(require '[planck.shell :refer [sh]])

(sh "echo" "hello")

It returns a map containing the exit code and the results of stdout and stderr.

{:exit 0
 :out "hello\n",
 :err ""}

Remember to separate the command name from its arguments (i.e. (sh "ls" "-al") instead of (sh "ls -al")); otherwise a cryptic “launch path is not accessible” error is shown.

Passing arguments to the script

If you invoke your script with arguments, all the arguments are stored in *command-line-args*.

(pr *command-line-args*)

When saved and run as planck script.cljs time for an argument, this will print ("time" "for" "an" "argument").

Reading files

ClojureScript – unlike Clojure – doesn’t have the slurp builtin since it mainly targets browser JavaScript. Planck helpfully includes this in the planck.core namespace, which can be used like so:

(require '[planck.core :refer [slurp]])

(slurp "path/to/myfile")

Fetching web pages with slurp

A nice bonus feature of slurp is its support for URLs – just give it a URL and it’ll return the response body as a string.

(slurp "https://myresourc.es/data.json")

Reading from standard input

In my scripts, I try to read from standard input and write to standard output as far as possible. This makes it easy to compose multiple shell scripts. slurping *in* does the trick:

(require '[planck.core :refer [*in* slurp]])

(pr (str "Planck says: " (slurp *in*) "!"))

Saving this as script.cljs and running

echo -n whoa | planck script.cljs

will print Planck says: whoa! on the terminal.

JSON parsing and serialisation

This is where Planck being a ClojureScript REPL helps a lot – you don’t need an external dependency to parse and serialise JSON! Good old JSON.parse and JSON.stringify from JS-land are available directly.

(.parse js/JSON "[1, 2, 3, 4]")
;; => #js [1 2 3 4]

(.stringify js/JSON #js [1 2 3 4])
;; => "[1,2,3,4]"

Note that while (.parse js/JSON "[]") is the better syntax for JS interop, (JSON.parse "[]") also works with the caveat that it doesn’t warn you if JSON has been overridden in your code somewhere. I often find myself using the latter though since it is more succinct.

Converting JS objects to Clojure data structures

You may have noticed the #js-tagged results in the previous example. We don’t want to deal with those! We want to be able to use all of the lovely Clojure vector and map manipulation functions in our scripts. Luckily, ClojureScript comes with two aptly-named helpers for just that.

js->clj converts JS objects to equivalent Clojure ones:

(js->clj #js [1 2 3 4])
;; => [1 2 3 4]

(js->clj #js {:x 1, :y 2})
;; => {"x" 1, "y" 2}

;; keywordizing map keys is super useful
(js->clj #js {:x 1, :y 2} :keywordize-keys true)
;; => {:x 1, :y 2}

clj->js works similarly, but in the opposite direction:

(clj->js [1 2 3 4])
;; => #js [1, 2, 3, 4]

Using the threading macro

The Clojure threading macro inverts nested function calls to “flatten” them out. I do most of my manipulations this way.

;; instead of
(select-keys
  (js->clj
    (JSON.parse (slurp "data.json"))
    :keywordize-keys true)
  [:x :y])

;; try the more Unix-y
(-> "data.json"
    slurp
    JSON.parse
    (js->clj :keywordize-keys true)
    (select-keys [:x :y]))

It reads a lot better, making it much easier to visualise the data transformations. It’s also more consistent with how you’d use pipes on the command line when manipulating input with various Unix tools.

Putting it all together

Here’s a script that reads in a JSON string, parses it, and returns the sum of the values of the "x" key from every object in the list.

#!/usr/bin/env planck

(require '[planck.core :refer [*in* slurp]])

(def in (-> *in*
            slurp
            JSON.parse
            (js->clj :keywordize-keys true)))

(->> in
     (map :x)
     (apply +)
     pr)

Save this as script.cljs and make it executable using chmod +x script.cljs. Running the following command

echo '[{"x": 1, "y": 2}, {"x": 3, "y": 4}]' | ./script.cljs

should print 4 on the terminal.

That’s it! Besides what I’ve described here, Planck has many more nifty features – check them out on the Planck User Guide and take it for a spin!

Looking for more posts? Check out the archives.