Friday, March 30, 2007

Adding named parameters to Factor

Christopher Diggins shows some C# code to convert code that uses named parameters into stack code. The same algorithm can be implemented in Factor:
! Based on
USING: kernel namespaces arrays sequences sequences-internals
math inference parser words quotations ;
IN: locals

: arg-list, ( n -- )
dup , [ f <array> ] %
<reversed> [ , [ swapd pick set-nth-unsafe ] % ] each ;

: arg-n, ( n -- ) , [ r> dup >r nth-unsafe ] % ;

: localize ( args obj -- )
tuck swap index dup -1 > [ nip arg-n, ] [ drop , ] if ;

: point-free ( quot args -- newquot )
dup length arg-list,
\ >r , swap [ localize ] each-with [ r> drop ] %
] [ ] make ;

: with-locals ( quot locals -- ) point-free call ;

\ with-locals 2 [ point-free ] define-transform

DEFER: |LOCALS delimiter

#! Syntax: LOCALS| a b c | ... a ... b ... c ... |LOCALS
"|" parse-tokens
[ create-in dup define-symbol ] map >r
\ |LOCALS parse-until >quotation parsed
r> parsed
\ with-locals parsed ; parsing

PROVIDE: libs/locals ;

The fact that this is so easy to implement inside the language itself is no fluke. The above code uses parser features only found in Factor 0.89, so you need the latest darcs code if you want to run it; I added it to the library as libs/locals.

It works by placing all the inputs to a LOCALS| ... | ... |LOCALS form in an array, which is moved to the retain stack. Named parameter accesses are rewritten into code which moves this parameter array to the data stack, pulls out the right element, and moves it back to the retain stack.

Here is a usage example:
: quadratic LOCALS| x a b c | a x sq * b x * + c + |LOCALS ;

Factor already has (dynamically scoped) variables in the core; using Eduardo's libs/vars syntax sugar, we could write the above code as:
VARS: x a b c ;
: quadratic [ >c >b >a >x a x sq * b x * + c + ] with-scope ;

However LOCALS| ... |LOCALS is more efficient (and less flexible) because it performs all lookup at compile time -- using variables entails hashtable creation and access. Also, with LOCALS| don't have to define the parameters as symbols first; the LOCALS| parsing word does that for you.

Personally I don't use local parameters in Factor. I think they are a crutch and result in poor reusability. However I won't stop people from using whatever coding style they find most satisfying, and I think having more options is good; especially since it is easy to define new abstractions in library code, without complicating the core language.


Frank said...

Is there any particular reason why you chose "LOCALS|" and "|LOCALS" rather than something like "LOCALS[" and "]LOCALS" ?

I found your example slightly clumsy to visually parse because of the use of "|" as both an indicator of the start/end of the block, and as a separator between parameter names and code.

Daniel Ehrenberg said...

An alternative strategy of defining this, which I would probably prefer, would be to create an alternative definition syntax which does this transform regardless of whether the definition is compiled. The compiler does not need to be involved here, and there is never a need to build the quotation when it is called. (Either way, you should probably make a custom see instance.)

But there's one thing I don't understand: how is this compatable with use in code of >r r> and with combinators?

It's probably not just a coincidence that this is the same basic strategy I used with libs/infix a while ago (except I left the array on the stack, since no stack operations were allowed anyway). This is probably the natural way of doing it. But I'm still wondering about real lexical scope...

Slava Pestov said...

Frank, you are right, LOCALS[ | ]LOCALS would look better.

Dan, you are right too, a syntax for defining words which take named parameters would be a tad more efficient.

It does not work if the word uses >r and r>.

I don't intend to put more effort into this library because I probably won't use it; but anybody is free to hack on it and send me a patch.