noflet - oo meets fp
Lisp is a powerful language. People have developed Object Oriented features in Lisp for some time. One of the first complete OO systems was CLOS, Common Lisp's Object System.
Lisp is also often used in a functional way.
But rarely do the functional and the OO styles mix. OO tends to be a bit more assigny, with lots of setq's to save state.
I rarely want the assigny type of OO programming but I often want some OO techniques in my functional programming. One of the main things I miss is decorating through overloading. Here's what I mean in Java; firstly class A:
class A { public int methodA(int x) { return 10 * x; } }
and now class B:
class B extends A { public int methodA(int x) { if (x < 1000) { return super.methodA(x); } else { return 20 * x; } } }
I quite like this. It's especially useful when testing.
I can't have it in Lisp very easily. At least in EmacsLisp.
Ta Da! NOFLET!
Of course I can have it in Lisp easily. I just make a macro.
I've built the noflet emacs-lisp package to let me do this.
noflet is very similar to the standard Lisp flet
. It let's you
define functions in the same way you define variables:
(let ((a 1))
(noflet ((my-func (x)
(* a x)))
(my-func 10)))
This defines a function my-func
, which only exists for the time
the let
-body is executing.
noflet
is dynamically bound, so it's good for overriding
functions:
(defun my-func (x) (* x 10)) (noflet ((my-func (x) 100)) (my-func 7)) ;; => 700
But the reason for noflet
's existance is that it can provide
access to a binding it is shadowing:
(defun my-func (x) (* x 10)) (noflet ((my-func (x) (if (< x 10) 100 ;; else call the old defn (funcall this-fn x)))) (my-func 7)) => 700
This is just like using super in Java. this-fn is the original value of the function my-func, that is being shadowed by the noflet definition which now calls the original!
This is particularly useful.
Mock a sub-class of a thing
We can mock file functions, for just a namespace, for example:
(noflet ((find-file (filename) (if (starts-with filename "/fake/") (get-buffer fake-file) ;; else do normal find-file (funcall this-fn filename))) ...) (find-file "/fake/file"))
If the file name starts with "/fake/" a static buffer is returned, otherwise we do whatever normally happens. This is great for testing complex things like files or processes.
Decorate a function's result
(noflet ((find-file (filename) (with-current-buffer (funcall this-fn filename) (encode-coding-region (point-min) (point-max) 'utf-8)))) (find-file "~/not-utf-8-yet"))
Here we force encoding to UTF-8 by wrapping the result from the origin function.
Combining the two
Of course, you can combine these two techniques:
(noflet ((find-file (filename) (if (starts-with filename "/fake/") (with-current-buffer (funcall this-fn filename) (encode-coding-region (point-min) (point-max) 'utf-8)) ;; Else do the normal (funcall this-fn filename)))) (find-file "~/fake/not-utf-8-yet"))
This adds automatic utf-8 encoding only to files in the "/fake/" namespace.
Using noflet
noflet
is just a package and like any other package it can be
installed from marmalade-repo and
depended on with other packages.
If you build your elisp as packages the following is the magic package
header you need to let you depend on noflet
:
;; Package-requires: ((noflet "0.0.5"))
Of course you still need to:
(require 'noflet)
in your code.
noflet controversy
noflet
is also helping me with a bit of Emacs controvesy. Stefan
Monnier, the maintainer of Emacs, has decided, in his wisdom, that the
existing Emacs CommonLisp compatibility library needs a lot of
change and some of the change breaks flet
usage. I don't like
that. I use flet
a lot.
But now I don't need to use flet
, now I can use noflet
.
One worry about this is that it results in an increasingly personal
Lisp. This is a worry because one doesn't want one's code to be
unreadable by anyone but one's self. But all coding is a social
problem, maybe the change occurring to the cl
library and
flet
is disturbing enough to cause readability and portability
concerns of it's own. In this case perhaps noflet
is a good thing.