Something Same

Language, Expression and Design

Wednesday

01

January 2014

Give your clojure workflow more flow

by Chris Zheng,

My profiles.clj has been steadily growing in size as I pick up little bits and pieces of clojure goodness to add to my development setup. It feels like this file could potentially be as important as your .bashrc file for programmer productivity. Recently, I took some time to refactor my helper functions into a library - vinyasa. This project contains helper functions for better organisation of profiles.clj.

Tools for Workflow:

When I am working with code, I want to have the following functionality:

  • Quick and Dirty Debugging
  • Import from Clojars without Repl Restart
  • Interacting with Leiningen Functions without Shell
  • Refreshing a Clobbered Namespace without Repl Restart
  • Immediate Pretty Printing, Source and Documentation Functionality
  • Good Exception Reporting

Lots of very good work has been done on different parts of the workflow. I wanted something that would tie everything together. I wanted all those to be defined in the profiles.clj. My complete profiles.clj file can be seen here:

{:user {:plugins [[lein-cljsbuild "1.0.0"]
                  [lein-clojars "0.9.1"]
                  [lein-midje    "3.1.3"]
                  [lein-midje-doc "0.0.18"]
                  [codox "0.6.6"]]
         :dependencies [[spyscope "0.1.4"]
                        [org.clojure/tools.namespace "0.2.4"]
                        [io.aviso/pretty "0.1.8"]
                        [im.chit/vinyasa "0.1.2"]]
         :injections [(require 'spyscope.core)
                      (require 'vinyasa.inject)

                      (vinyasa.inject/inject 'clojure.core
                        '[[vinyasa.inject [inject inject]]
                          [vinyasa.pull [pull pull]]
                          [vinyasa.lein [lein lein]]
                          [clojure.repl apropos dir doc find-doc source 
                                        [root-cause cause]]
                          [clojure.tools.namespace.repl [refresh refresh]]
                          [clojure.pprint [pprint >pprint]]
                          [io.aviso.binary [write-binary >bin]]])

                      (require 'io.aviso.repl 
                               'clojure.repl 
                               'clojure.main)
                      (alter-var-root #'clojure.main/repl-caught
                        (constantly @#'io.aviso.repl/pretty-pst))
                      (alter-var-root #'clojure.repl/pst
                        (constantly @#'io.aviso.repl/pretty-pst))]}}

Lets go through each criteria and how it has been defined in the original file:

quick and dirty debugging

This is easy, spyscope is an excellent library for doing this.

We add in our dependencies:

 :dependencies [[spyscope "0.1.4"]
                ...]
 :injections [(require 'spyscope.core)
               ...]

More information can be found on the github page. This tool is very smart and allows for fast, dirty debugging.

import libraries without restart

How many times have you forgotten a library dependency for project.clj and then had to restart your nrepl? pull is a convienient wrapper around the pomegranate library:

(require 'hiccup.core)
;; => java.io.FileNotFoundException: Could not locate hiccup/core__init.class or hiccup/core.clj on classpath:

(require 'hiccup.core)
(pull 'hiccup)
;; => {[org.clojure/clojure "1.2.1"] nil, 
;;     [hiccup "1.0.4"] #{[org.clojure/clojure "1.2.1"]}}

(use 'hiccup.core)
(html [:p "hello World"])
;; => "<p>hello World</p>"

(pull 'hiccup "1.0.1")
;; => {[org.clojure/clojure "1.2.1"] nil, 
;;     [hiccup "1.0.1"] #{[org.clojure/clojure "1.2.1"]}}

We inject it into our clojure.core namespace via:

:dependencies [[im.chit/vinyasa "0.1.2"]]
:injections [(require 'vinyasa.inject)            
            (vinyasa.inject/inject 'clojure.core
              '[...
                [vinyasa.pull pull]
                ...])

leiningen within the repl

Don't you wish that you had the power of leiningen within the repl itself? lein is that entry point. You don't have to open up another terminal window anymore, You can now run your commands in the repl!

(lein)
;; Leiningen is a tool for working with Clojure projects.
;;
;; Several tasks are available:
;; check               Check syntax and warn on reflection.
;; classpath           Write the classpath of the current project to output-file.
;; clean               Remove all files from paths in project's clean-targets.
;; cljsbuild           Compile ClojureScript source into a JavaScript file.
;;
;;  .....
;;  .....

(lein javac)       ;; Compile java classes

(lein install)     ;; Install to local maven repo

(lein uberjar)     ;; Create a jar-file

(lein push)        ;; Deploy on clojars I still use lein-clojars 

We inject it into our clojure.core namespace via:

:dependencies [[im.chit/vinyasa "0.1.2"]]
:injections [(require 'vinyasa.inject)            
            (vinyasa.inject/inject 'clojure.core
              '[...
                [vinyasa.lein lein]
                ...])

refreshing namespace without restart

Stuart Sierra has done alot of work on this already with tools.namespace. We will inject it into clojure.core as well:

:dependencies [[im.chit/vinyasa "0.1.2"]
               [org.clojure/tools.namespace "0.2.4"]]
:injections [(require 'vinyasa.inject)            
            (vinyasa.inject/inject 'clojure.core
              '[...
                [clojure.tools.namespace.repl refresh]
                ...])

pprint, source, doc, etc...

These are old functions taken from the clojure.pprint and clojure.repl namespaces. Because alot of libraries have pprint already defined, we prefix it with > so that it does not set off any warnings:

:dependencies [[im.chit/vinyasa "0.1.2"]]
:injections [(require 'vinyasa.inject)            
            (vinyasa.inject/inject 'clojure.core
              '[...
                [clojure.repl apropos dir doc find-doc source [root-cause cause]]   
                [clojure.pprint [pprint >pprint]]
                ...])

pretty exceptions

The best stacktrace output library is pretty. We replace the old stacktrace library with the pretty version using this:

:dependencies [[io.aviso/pretty "0.1.8"]]
:injections  [(require 'io.aviso.repl 
                       'clojure.repl 
                       'clojure.main)

              (alter-var-root #'clojure.main/repl-caught
                (constantly @#'io.aviso.repl/pretty-pst))
              (alter-var-root #'clojure.repl/pst
                (constantly @#'io.aviso.repl/pretty-pst))]

So there you go. We are now tooled up and ready to develop clojure code even faster than ever!

Future Considerations

  • use with the reloaded workflow typified by jig and component
  • inspection of classpath, jars
  • doing an oh-my-zsh style plugin system for profiles.clj
  • including an inspector (clojure.inspector or clj-ns-browser)
comments powered by Disqus