Revisiting the Linear recursion combinator in Factor four years on
The Joy programming language has a linrec combinator for performing linear recursion. Some time ago I took a stab at implementing linrec in Factor. Slava also posted his version that was originally a part of Factor.
As can be seen from Slava's version, juggling the stack when four quotations are involved can be problematic. I thought I'd revisit linrec using Factor's locals library and see how it compares. This is my attempt at an implementation using locals:
Working on this example I can say that, for me, Factor programming has become easier compared to four years ago.
Categories: factor
As can be seen from Slava's version, juggling the stack when four quotations are involved can be problematic. I thought I'd revisit linrec using Factor's locals library and see how it compares. This is my attempt at an implementation using locals:
:: linrecThis is pretty readable compared to the solutions in the prior posts. Because linrec is a combinator it needs to be declared 'inline'. It recursively calls itself so needs to be declared 'inline recursive' to enable calls of linrec to be compiled. More details about this can be found in the Factor documentation. Usage of the linrec word looks like:
( if-quot: ( -- ? )
then-quot: ( -- )
else1-quot: ( -- )
else2-quot: ( -- )
-- )
if-quot call [
then-quot call
] [
else1-quot call
if-quot then-quot else1-quot else2-quot linrec
else2-quot call
] if ; inline recursive
5 [ dup 1 = ] [ ] [ dup 1- ] [ * ] linrec .It's nice that the 'fac' word and the linrec definition seem to compile down to the same code:
=> 120
[ 5 [ dup 1 = ] [ ] [ dup 1- ] [ * ] linrec ] infer.
=> ( -- object )
{ 1 2 3 } [ dup rest empty? ] [ first ] [ rest ] [ ] linrec
=> 3
[ { 1 2 3 } [ dup rest empty? ] [ first ] [ rest ] [ ] linrec ] infer.
=> ( -- object )
[ 1000 [ dup 1 = ] [ ] [ dup 1- ] [ * ] linrec drop ] time
=> 22ms
: fac ( n -- n ) dup 1 = [ dup 1- fac * ] unless ;
[ 1000 fac drop ] time
=> 19ms
[ [ dup 1 = ] [ ] [ dup 1- ] [ * ] linrec ] optimized.One confusing aspect of this linrec definition is having to declare the stack effects of the quotations passed to linrec. Depending on the linear recursion task the stack effects may be different. Compare factorial vs finding the last element of a sequence:
=> [
\ ( gensym ) [
dup 1 dupd eq?
[ drop t ]
[ dup 1 swap tag eq? [ 1 bignum= ] [ drop f ] if ] if
[ ] [ dup 1 - ( gensym ) * ] if
] label
]
\ fac optimized.
=> [
dup 1 dupd eq?
[ drop t ]
[ dup 1 swap tag eq? [ 1 bignum= ] [ drop f ] if ] if
[ ] [ dup 1 - fac * ] if
]
{ [ dup 1 = ]
[ ]
[ dup 1- ]
[ * ]
} [ infer. ] each
=> ( object -- object object )
( -- )
( object -- object object )
( object object -- object)
{
[ dup rest empty? ]
[ first ]
[ rest ]
[ ]
} [ infer. ] each
=> ( object -- object object )
( object -- object )
( object -- object )
( -- )Both usages of linrec still infer and compile so I guess the fact that the stack effects balance out allows this. I'm not sure whether it's just a side effect of implementation or intended.Working on this example I can say that, for me, Factor programming has become easier compared to four years ago.
Categories: factor
Labels: factor

0 Comments:
Post a Comment
<< Home