Bluish Coder

Programming Languages, Martials Arts and Computers. The Weblog of Chris Double.


2008-10-11

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: :: linrec ( 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

This 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:

5 [ dup 1 = ] \[ \] [ dup 1- ] [ * ] linrec .
 => 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

It’s nice that the ‘fac’ word and the linrec definition seem to compile down to the same code:

[ [ dup 1 = ] \[ \] [ dup 1- ] [ * ] linrec ] optimized.
  => [
      \ ( 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
  ]

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:

{ [ 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.

Tags


This site is accessable over tor as hidden service mh7mkfvezts5j6yu.onion, or Freenet using key:
USK@1ORdIvjL2H1bZblJcP8hu2LjjKtVB-rVzp8mLty~5N4,8hL85otZBbq0geDsSKkBK4sKESL2SrNVecFZz9NxGVQ,AQACAAE/bluishcoder/-7/


Tags

Archives
Links