My idea of proper literate programming is that I write code as I write a novel. It's a single file that I write mostly in a linear way. When I make jumps, I warn the reader that I'll make a jump. So the whole code can be read from cover to cover, so to speak. Reading the code follows my way of thinking, how I changed my mind, how I added code to implement new requirements, how I fixed bugs, etc. The following snippet shows two example sessions of such a code editing. There is no tool yet to produce proper, working source code from it.
; I just want a simple hello-world function. (Note the ! in next
; line: it shows it is an editing command, not part of the final code)
;! (open "src/hello/core.clj")
; The file is opened. If the file or its parent directories don't
; exist, they are created.
(ns hello.core)
(defn greet
"Just return a greeting"
[]
"Hello")
; It's time now to test what we've just written
;! (open "test/hello/core_test.clj")
(ns hello.core-test
(:use
[clojure.test :only [deftest is]])
(:require
[hello.core :as core]))
(deftest greeting-test
(is
(= "Hello" (core/greet))))
; That's it. We are done, we can commit our code...
;----------------------------------------------------
; The customer wants our greeting to be able to accept a `fellow`
; argument to whom the greeting is addressed. So we have to add a
; test case for that. Since code is data, we just have to refactor
; it. It's usually done in an editor without much thought beforehand.
; You may use some support from the editor, like `paredit`. But I
; want to do differently this time. I'm coding yet, just thinking
; about what I would do in the editor. This thinking happens to be
; some code too.
; Open the file with the greeting test (it already exists now).
;! (open "test/hello/core_test.clj")
; Find the test.
;! (find 'deftest 'greeting-test)
; Go to the first assertion
;! (zoom 'is)
; We want to append a new test case at the same level
;! (up)
;! (append-next-block)
(is
(= "Hello, Mary" (core/greet "Mary")))
; You can guess what the final code looks now.
; We can do something more difficult now, change the greeting function
; itself. We want to have both a 0, and a 1 argument version.
; Now you know how to navigate where we want to change the code.
;! (open "src/hello/core.clj")
;! (find 'defn 'greet)
;! (zoom '[])
; We want to make this: [] "Hello" => ([] "Hello")
;! (wrap-round)
; It just wraps the arg vector. We have extend the paren to
; include the "Hello" part.
;! (slurp-forward)
; We are done with the 0 argument version. We just have to add
; code for 1 one argument.
;! (append-next-block)
([fellow]
(str "Hello, " fellow))
; This modification is done, we can commit the changes.
And this is how the files would look now:
(ns hello.core)
(defn greet
"Just return a greeting"
([]
"Hello")
([fellow]
(str "Hello, " fellow)))
(ns hello.core-test)
(deftest greeting-test
(is
(= "Hello" (core/greet)))
(is
(= "Hello, Mary" (core/greet "Mary"))))
No comments:
Post a Comment