My Clojure Workflow, Reloaded
thinkrelevance.com
thinkrelevance.com
This is the key point people some comments have been missing. Since you can evaluate code whenever you want, the running instance might become a frankenstein, containing code and data that shouldn't be around anymore.
I'd like to see the point of view of Smalltalk users (who develop against the images).
All the talks about live editing is nothing more than recovering the workflows we already had in Smalltalk and Lisp machines.
If you destroy the environment, just restart not a big deal.
If you already saved the environment into the image, just rollback to the previous image snapshot, not a big deal specially nowadays that image based source control systems are available, like Monticello and Metacello.
tl;dr it is not much of an issue
So it's like working with Ruby's irb, or with iPython, except that the integration with Emacs is so freaking nice. So the whole workflow ends up being a back and forth between the code buffer and the repl buffer. And it happens really fast too.
Clojure does have a slow startup, thanks to the JVM. It's not so bad on capable computers though. But that's not the point. The point is that having to restart a repl session interrupts your flow.
Note: I only played with Clojure, haven't done anything serious in it. My favourite is Scala and it would be nice if the Scala repl would do a fast reload on file changes. I don't need it in Scala though, as in Scala you tend to rely less on the repl.
(defun nrepl-reset ()
(interactive)
(nrepl-interactive-eval "(user/reset)"))
The original elisp function: (defun nrepl-reset ()
(interactive)
(set-buffer "*nrepl*")
(goto-char (point-max))
(insert "(user/reset)")
(nrepl-return))I mainly play with Clojure for little things, but still, . . . I'm impressed. Heck, I barely use two or three commands out of nrepl.el
(defn foo
([x] "bar")
([x y] "baz"))
? (defn foo
([x] *docstring* "bar")
([x y] *docstring* "baz"))
because you want to be able to document the different implementations differently. The semantics would be: If the function body starts with a string literal, the latter serves as the documentation. If the body consists of any other code following the string literal, it will be ignored for run-time processing; if it's the only thing in the body, it also serves as the return value.I disagree with this. Since the normal functions cannot dispatch on type⁺, only arity, the different implementations are typically very related and should share same documentation. Your proposal would lead to massive duplication, and reduce the likelihood of people actually documenting the functions.
⁺ There are two separate mechanisms for providing richer function dispatch, both allow more granular documentation.
However, if this only applies to different arities, please also compare to the following style:
(defun function (a b &optional c d e)
"Documentation string"
"return value"))
I still think that the function signature is more readable when the doc string comes after the parameters, but perhaps it's just a matter of taste.The above is obviously not Clojure. But as I commented originally, I just wasn't aware that this is the way Clojure does it.
However, matching by arity at the front really helps make simple functions clean. I really like the multiple body approach, as in my code there's typically one "standard" case, and the rest are special cases that just call the standard one with new params. Having to deal with a rest arg with potentially multiple possible lengths just doesn't look as clear in practice.
It's all tradeoffs. I can see why you'd like the arglist first -- I have some Haskell background, and I do miss it's types in Clojure, just for the documentation they provide.
(defn some-string []
"is this a docstring or a return value?")Clojure docstrings need to precede the argument list. e.g. this is correct:
(defn foo
"Some docstring"
[]
"Some return value"))
But this is not: (defn foo
[]
"Some docstring"
"Some return value"))
It will still compile, but the first string will be treated as an expression, and therefore ignored as it has no side effects.The reason docstrings in Clojure come before the arguments, is that because if they came after, their effect would be ambiguous, regardless of arity. e.g.
(defn foo [] "foo")
Is the string supposed to be the return value of the function, or its docstring?- If the function body starts with a string literal, it is always the doc-string
- If that string is the only thing in the function body, it is also the return value.
http://docs.racket-lang.org/reference/eval-model.html#%28par...
[1] And by "a bit ridiculous", I mean completely archaic.
For example, if your application includes some state from previous actions. Then you change a function that acts on that state, but with a different protocol (e.g. data structure expectations changed in the function). Then your application would not work, as the old state is invalid for the new function. With techniques like explained in the article, you can quickly "reboot" your state to be consistent with your new code by reseeding your system.
I really don't see a good pain/gain ratio here. Clojure being a lisp you are already good for reloading functions. Only if you change data structures or macros you need to reboot. That I would think is a much less frequent operation for which a JVM restart is acceptable. (You also know that absolutely everything got reset.)
If restarting your JVM is ok with you, go for it! But I like what Stuart has presented and find it interesting, I'm all about minimizing interruptions while I'm working on something and will be trying to adapt his workflow to mine.
user=> (use '[clojure.tools.namespace.repl :only (refresh)])
user=> (refresh)
What is described in the article is a little more advanced and has to do with how to control how your reloaded code deals with the old state.
His workflow goes much deeper than just changing the code. The entire application state is abstracted away and you can reload the entire application state in under a second.
Think a little about the implications of that before posting.