Bluish Coder

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


2006-09-16

Factor Lazy Lists Revisited

I originally wrote the Lazy Lists library in Factor to support the Parser Combinators library. It was one of my first libraries in Factor and was probably not very well written. Matthew Willis worked on it for the recent Factor releases to get it working with the removal of the standard list datatype and made it a lot better in places.

Both the lazy lists and parser combinators worked by building up quotations manually to defer computation, and having these quotations called when required. This unfortunately made the library a bit difficult to debug and understand. While using the parser combinators library for a recent project I found it quite hard to work some things out and found a couple of bugs in the parser combinators - and areas where the lazy lists weren't lazy enough.

To resolve the issues I rewrote the lazy lists library in a slightly different style and plan to redo the parser combinators in a similar manner. Instead of building up quotations I now use tuples to hold the information, with generic functions to call to process them.

I created a generic function based protocol for lists. It has three generic functions:

GENERIC: car  ( cons -- car )
GENERIC: cdr  ( cons -- cdr )
GENERIC: nil? ( cons -- bool )

A 'cons' tuple for normal non-lazy lists, with the obvious implementation was the starting point:

TUPLE: cons car cdr ;
M: cons car ( cons -- car )
    cons-car ;    

M: cons cdr ( cons -- cdr )
    cons-cdr ;    

: nil ( -- cons )
  T{ cons f f f } ;

M: cons nil? ( cons -- bool )
    nil eq? ;

This gives the functionality of ordinary lists that used to exist in Factor. To make actual lazy lists I use the 'force' and <promise> words from Matthew's lazy list rewrite. A <promise> is a wrapper around a quotation that calls that quotation when 'force' is called on it, and memoizes the value to return directly on later 'force' calls. A lazy list has a promise for both the 'car' and 'cdr':

: lazy-cons ( car cdr -- promise ) 
    >r <promise> r> <promise> <cons> 
    T{ promise f f t f } clone [ set-promise-value ] keep ;

M: promise car ( promise -- car )
  force car force ;

M: promise cdr ( promise -- cdr )
  force cdr force ;

M: promise nil? ( cons -- bool )
  force nil? ;

Notice that the lazy list itself is a promise. And the methods are specialized on the <promise> type. So not only are the 'car' and 'cdr' promises, but so is the list itself. The 'cdr' of a lazy list is a promise, which when forced, returns something that conforms to the list protocol. This means parts of the list can be lazy, and parts non-lazy

A lazy map operation is the first word I implemented on top of this generic protocol. Previous implementations used quotations that were automatically generated and were quite complicated. In this new implementation I created a <lazy-map> tuple that implemented the list protocol which turned out to be much simpler:

TUPLE: lazy-map cons quot ;

: lmap ( list quot -- result )
    over nil? [ 2drop nil ] [ <lazy-map> ] if ;

M: lazy-map car ( lazy-map -- car )
  [ lazy-map-cons car ] keep
  lazy-map-quot call ;

M: lazy-map cdr ( lazy-map -- cdr )
  [ lazy-map-cons cdr ] keep
  lazy-map-quot lmap ;

M: lazy-map nil? ( lazy-map -- bool )
  lazy-map-cons nil? ;

Basically the 'lmap' call itself just returns a <lazy-map> which contains the quotation and list to map over. Calls to 'car' will call the quotation on the head of the list, while 'car' returns a new <lazy-map> that holds the same quotation and the 'cdr' of the original list.

The 'take' operation returns the first 'n' items in the list (whether lazy or not). It's lazy implementation is:

TUPLE: lazy-take n cons ;

: ltake ( n list -- result )
    over zero? [ 2drop nil ] [ <lazy-take> ] if ;

M: lazy-take car ( lazy-take -- car )
  lazy-take-cons car ;

M: lazy-take cdr ( lazy-take -- cdr )
  [ lazy-take-n 1- ] keep
  lazy-take-cons cdr ltake ;

M: lazy-take nil? ( lazy-take -- bool )
  lazy-take-n zero? ;

Given a word 'naturals' to return an infinite list of natural numbers, the squares of the first 10 numbers can be returned as:

naturals [ dup * ] lmap 10 swap ltake

The result is a lazy list itself. No actual computation is done yet until 'car' or 'cdr' is called on the result.

More code implements 'subset' to filter out items from the lists, append lazy lists in a lazy manner, etc. The squares of all odd natural numbers are expressed as:

naturals [ odd? ] lsubset [ dup * ] lmap

It would be possible to add to this to implement the protocol for other things like lines in a file. This way you can lazily read through a large file line by line. Although this could be done in the previous lists code I think this approach makes things easier to read. As part of the refactoring I also wrote integrated Factor help for all the non-internal words.

I'm not sure how 'concatenative' this approach is or whether it's the right approach for 'stack' based languages but it seems to work well for Factor. I'll work on the parser combinators, fitting them into a similar style, and see how it works out with the current project.

Tags: factor 

2006-09-12

Javascript with Advanced Continuation Support

Bill Clementson has an announcement for the next Vancouver Lisp Users Group meeting. This one is about a web framework called jwacs, Javascript with Advanced Continuation Support.

It's an interesting library that seems to fit into a similar space to Narrative Javascript. It compiles a superset of Javascript, with support for continuations, into normal Javascript which is then run on the browser. Their demo page has a number of examples with source. The compiler from jwacs to Javascript is written in Common Lisp.

It looks to me like a number of people are starting to use the 'asynchronous operations written in a synchronous style' in Javascript on the browser, bringing the good things that you get with continuation based web servers to the client side.

Tags: commonlisp 

2006-09-07

Serializable Gadgets

As Slava has noted, I've managed to get the serialization code to the point where it serializes user interface gadgets. To demonstrate it I serialized an in-progress game of Space Invaders, uploaded the serialized file to my web server and someone else on IRC downloaded it, deserialized it, and continued where the game left off.

Given the gadget on the stack, serialization to a file was as easy as:

[ 
  "filename.ser" &lt;file-writer> [ 
    unparent serialize 
  ] with-stream 
] with-serialized

To get the instance running again:

[ 
  "filename.ser" &lt;file-writer> [ 
    deserialize 
  ] with-stream 
] with-serialized "Space Invaders" open-titled-window
Tags: factor 

2006-09-05

Pattern Matching in Factor

For the concurrency library in Factor I've struggled to come up with good ways of writing the main loop of processes that handle messages. Erlang makes it so nice being able to create tuples and break them apart using pattern matching.

I've gone from using Factor arrays and breaking them apart with 'first', etc to using generic functions. The latter works well but ends up quite verbose for small servers. You need to create tuple types for the different messages - and handling tagged messages to reply to had to be done differently to other messages.

To make this sort of thing easier I've written a match word for Factor that does pattern matching. I based it on code from Paul Graham's excellent Common Lisp book, On Lisp.

Given a Factor sequence or primitive type you can pattern match and create bindings to symbols with a special format. Any symbol that begins with a '?' character is used as a pattern match variable, and binds to the value in a matching sequence. Here's a simple example:

"match" require
USE: match

SYMBOL: ?a
SYMBOL: ?b
SYMBOL: ?c

{ 1 2 3 } { ?a ?b ?c } match . 
  => H{ { ?a 1 } { ?b 2 } { ?c 3 } }

The two sequences match in that they are both sequences with three items. So the result is a hashtable containing the pattern match variables as keys and the values of those variables in the second sequence as the value. If items in the sequence are not pattern match variables then they must match exactly:

{ 1 2 3 } { 1 2 ?a } match .
  => H{ { ?a 3 } }

{ 1 2 3 } { 2 2 ?a } match .
  => f

The second example doesn't match as the first element in both sequences is not the vaue '2'.

Matching works recursively too:

{ 1 2 { 3 4 } { 5 6 } } { 1 2 ?a { ?b ?c } } match .
  => H{ { ?a { 3 4 } } { ?b 5 } { ?c 6 } }

This type of pattern matching is very useful for deconstructing messages sent to distributed factor objects. But even more useful is to be able to have something like 'cond' but working directly with patterns. This is what match-cond does. It takes a sequence and an array of arrays. Each sub-array contains the pattern to match the sequence against, and a quotation to execute if that pattern matched. The quotation is executed with the hashtable result of the pattern match bound in the current namespace scope, allowing easy retrieval of the pattern match variables.

It sounds complex but is actually very easy in practice. Here what an example 'counter' process might look like using 'match-cond':

SYMBOL: ?value
SYMBOL: ?from
SYMBOL: ?tag

: counter ( value -- )
  receive {
    { { increment ?value } [ ?value get + counter ] }
    { { decrement ?value } [ ?value get - counter ] }
    { { get ?from }        [ dup ?from get send counter ] }
    { _                    [ "Unmatched message" print counter ] }
  } match-cond ;

This is a process that keeps track of a count value. You can send messages to increment or decrement the count by an amount, or to get the value and send it back to the calling process. So sends/receives look like:

[ 0 counter ] spawn
{ increment 5 } over send
{ decrement 10 } over send
[ get , self , ] { } make swap send receive .
  => -5

This works out to be quite readable and much nicer than my previous attempts. The match and match-cond code is still a work in progress. I plan to add the ability to pattern match on tuples, all the factor sequence types, and to repeat pattern match variables to ensure the same repitition occurs in the target sequence. For example:

T{ person f "chris" "double" } T{ person f ?first ?last } match .
  => H{ { ?first "chris" } { ?last "double" } }

{ "one" "two" "one" "two" } { ?a ?b ?a ?b } match .
  => H{ { ?a "one" } { ?b "two" } }

{ "1" "two" "one" "two" } { ?a ?b ?a ?b } match .
  => f</pre>

The match library is split out from concurrency since it's likely to be quite usable for other things in Factor. It's available from my repository (and is likely to be available shortly from the main Factor repository). To get the patches from my repository:

darcs get http://www.bluishcoder.co.nz/repos/factor
Tags: factor 

2006-09-04



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


Tags

Archives
Links