Sunday 10 February 2013

How I screwed myself over with side-effects

RETCON TIME. Screw part 2 of the previous article, it wouldn't introduce anything useful - all the insights were presented in part one. And just copying some code and annotating it with obviousness misses the point a bit. And I forgot what that other thing is I wanted to write. After all, two months or so passed (yay me for my update schedule, by the way).

 Aaaaaaaaanyways.

Currently I'm into Common Lisp (you might have noticed by now that I'm kinda sold on that whole "crapton of parentheses" concept), which differs from Clojure in that it is not strictly functional. While true, that's a bit of an understatement. There are other significant differences as well. It doesn't have the highly derisive attitude towards side effects (aka. actually changing something) that Clojure has.

This causes the Common Lisp standard to frequently define two similar functions, with the difference that one of them is destructive but more efficient. What that means is, that for example (nreverse is the destructive variant, reverse is the "pure", or "functional" one):

(defvar *list* '(1 2 3 4 5))
(reverse *list*)
=> '(5 4 3 2 1)
*list*
=> '(1 2 3 4 5)
(nreverse *list*)
=> '(5 4 3 2 1)
*list*
=> '(1)

The last result it implementation-dependant. Basically, functions that the standard acknowledges to be destructive are allowed to do anything with their input, including taking it apart with a chainsaw and/or sending it to the moon. The implementation I'm using (Clozure CL 1.8) has a very understandable side effect - Lisp lists are pretty much linked lists, so when reversing, the previously first cell '(1) is the last one and points to nothing, though technically the result of evaluating *list* after calling (nreverse *list*) might as well be '(its a trap!) Of course I'm exaggarating. This would actually be probably not allowed, due to unnecessarily creating new cells and/or symbols. At least I think it would be sensible for the standard to forbid this. The obvious reason for this split is efficiency - why create a full copy, when you can make the reverse (or anything else) in place?

 (defmacro internal-step~ (sym &rest syms)
  (if (null syms)
    sym
    `(,sym (internal-step~ ,@syms))))


(defmacro ~ (&rest syms)
  `(internal-step~ ,@(nreverse syms)))

(defmacro ? (&rest form)
  (let ((sym (gensym)))
    `(let ((,sym ,form))
       (format t "? :: ~a => ~a~%" (mapcar #'(lambda (x) (if (listp x) "..." x)) ',form) ,sym)
       ,sym)))


Now, those three macros. The first two form something roughly equivalent to Clojure's -> macro. ? is my favourite debug macro, which outputs the result in some way I want it to, and then returns it.

As you see, ~ uses nreverse to reverse the input list. In my code, I had this little bit:

(when (< 0 (~ s get-levels length)
   ...)

For some reason it told me that "# is not of expected type REAL." This meant, it was using only the value of "s" (which indeed was a CLOS object), discarding the two additional calls. When I manually expanded (~ s get-levels length) in-repl, however, the correct (length (get-levels s)) was generated.

Did I mention that the effect of destructively operating on literals is not defined by the standard? Good. Because if you've been following along, this is exactly what I did here - I took the data literal '(s get-levels length) and put it through nreverse. Which in my implementation apparently turned the literal into '(s) (due to the aforementioned sensible destructive reversing process). Which obviously evaluates to a CLOS object, not a number.

And all because I was used to nreverse (due to being destructive often not being a problem) and didn't even consider what damage the side-effect could cause. And I just wasted almost half an hour on removing one single letter (changing the call from nreverse to reverse fixed it of course).

But hey, blog post idea for free!