Sunday, March 25, 2007

Double dispatch in 24 lines

A friend referred me to this video where Peter Seibel extols the virtues of Lisp over Java at Google. One of the big advantages he cited was multimethods, which are much better than the visitor pattern as used in Java. Though the visitor pattern already comes out more flexible and concise in Factor than Java, I took on the challenge of creating an abstraction over those visitor patterns to avoid repeating code. I wanted something that made this sort of code work (this is a pretty stupid example, though):

VISITOR: ++ ! acts like +, coercing string arguments to a number, unless both arguments are strings, in which case it appends them

V: number string ++
string>number + ;
V: string number ++
>r string>number r> + ;
V: number number ++
+ ;
V: string string ++
append ;

It would compile it to a form sort of like this:

GENERIC: ++-string
GENERIC: ++-number
M: string ++ swap ++-string ;
M: number ++ swap ++-number ;

M: number ++-string
swap string>number + ;
M: string ++-number
swap >r string>number r> + ;
M: number ++-number
swap + ;
M: string ++-string
swap append ;

As it turned out, it wasn't much of a challenge, and the resulting code is about 24 lines long. The main problem with it is that the visitor pattern lends itself to a rather naive method order, but I don't think that's much of an issue in practice. Without further ado, here's the visitor pattern in Factor:

USING: kernel generic syntax words parser hashtables sequences ;
IN: visitor

: define-visitor ( word -- )
dup dup reset-word define-generic
H{ } clone "visitors" set-word-prop ;

CREATE define-visitor ; parsing

: connect-method ( top-class generic method-word -- )
[ swap ] swap add -rot define-method ;

: record-visitor ( top-class generic method-word -- )
swap "visitors" word-prop swapd set-hash ;

: new-vmethod ( method bottom-class top-class generic -- )
gensym dup define-generic
3dup connect-method
[ record-visitor ] keep
define-method ;

: define-visitor-method ( method bottom-class top-class generic -- )
>r >r >r \ swap add* r> r> r>
2dup "visitors" word-prop hash
[ nip define-method ] [ new-vmethod ] ?if ;

: V:
! syntax: V: bottom-class top-class generic body... ;
f set-word scan-word scan-word scan-word
parse-definition -roll define-visitor-method ; parsing

Enjoy! The full module can be found in the Factor repository. The great thing about Factor here is that, whereas you'd have to repeat this pattern every time you use it in Java, Factor lets you write it only once. But then again, comparing Factor to Java to prove Factor is good is somewhat of a straw-man fallacy.

1 comment:

.:| B+ |:. said...
This comment has been removed by a blog administrator.