Here, to whet peoples appetites, is an example of some code written in Pop11.
It is the well-know shuffle-cards algorithm; here, it takes a list of thingies
and delivers a new list of thingies. First, the code by itself:
define shuffle( list ); lvars list, i;
lvars vec = {% list.dl %};
for i from vec.length by -1 to 2 do
lvars n = i.random;
unless n == i do (i.vec, n.vec) -> (n.vec, i.vec) endunless
endfor;
vec.destvector.conslist
enddefine;
And now the commentary.
define shuffle( list ); lvars list, i;
defines a procedure called shuffle; that is to say, a variable assigned the
value of the procedure. Because of the context (top-level), ``shuffle'' is a
permanent variable; it persists until it is explicitly cancelled. (Actually, it
persists until garbage-collected, which it won't be until it is (a) cancelled,
so there's no route to it through the symbol table, and (b) no references to it
remain in the system.)
Shuffle takes one argument, called ``list'', which is declared as a lexical
variable (``lvar''). [The default, for historical reasons, would be as a
dynamically local permanent variable; what Lisp would call a ``special''
variable'']. We take the opportunity to declare another local lexical variable,
``i''.
lvars vec = {% list.dl %};
``vec'' is another lexical, initialised to a vector (the ``{}'' brackets)
constructed by evaluating the expression (the ``%%'' semi-brackets) ``list.dl''
and gathering all the results up from the evaluation into the vector. The infix
``.'' is function application, with the function on the *right*; thus the
function (found in) ``dl'' is applied to (the value of) ``list''. ``dl'' takes
a list and explodes all of its elements onto the stack. (``dl'' stands for
``Destroy List'', although the list is not actually destroyed. ``Decompose'' is
nearer the mark.)
Hence ``vec'' becomes a vector of all the elements of ``list'', in the same
order.
for i from vec.length by -1 to 2 do
A simple counting-down for-loop. Sadly the control variable is not
automatically declared for us (roll on Pop9X). ``length'' is a general
procedure for finding the length of suitable things, such as vectors and lists.
lvars n = i.random;
``random'' is the library procedure for generating random numbers in the range
1 .. argument.
unless n == i do (i.vec, n.vec) -> (n.vec, i.vec) endunless
The only reason for the ``unless'' is to do nothing in the case where we'd
exchange an item with itself. ``do'' could be replaced by ``then''; the two are
equivalent. The general Pop style is to use closing keywords obtained by
sticking ``end'' on the front of the opening keyword.
``->'' is the assignment arrow; the target(s) is(are) on the *right*. Here we
see an assignment to a tuple, which means to assign to the components in
right-to-left order. Because Pop is stack-based, the values to be assigned are
waiting on the stack.
I said earlier that ``.'' was postfix application, yet here it is ``vec'' that
is being applied, and I claimed that ``vec'' was a vector. What's going on?
Well, in Pop *any* value can be applied. For vectors, it happens that
application means indexing; one day someone will explain how this is arranged.
Even supposing that application works for vectors, what's this assignment to a
function call? Haven't matters become even more obscure? Perhaps. In Pop,
procedures can come equipped with *updaters*, which are themselves procedures.
The expression ``-> F(X)'' means ``(updater(F))( X )'', ie, when a call appears
as the target of an assignment, the updater of the procedure is what gets
called, rather than the procedure itself. For vectors, this means that the
updater of the underlying indexing procedure is called, giving the desired
result.
[Compare Dylan where, for obscure reasons, updaters are associated with the
*name* of a procedure, rather than the *value* of the procedure; or Common
Lisp, where the nearest equivalent to updaters are the setf-macros.]
endfor;
And here's the closing keyword for the for-statement.
vec.destvector.conslist
``destvector'' explodes the elements of the vector onto the stack, and then
pushes the length of the vector. ``conslist'' takes an argument N, and the next
N elements off the stack, and makes them into a list, bottom-most elements at
the front of the list. Thus we have converted a vector to a list. I might
instead have written ``[% vec.explode %]'', similar to the initialisation
expression for ``vec'', but I wanted to make a different point. ``[]'' are list
brackets, just as ``{}'' are vector brackets.
enddefine;
And here's the closing keyowrd for the procedure definition. Trying it out:
shuffle( [a b c d] ) =>
** [d c b a]
shuffle( [a b c d] ) =>
** [c a d b]
shuffle( [a b c d] ) =>
** [b a c d]
shuffle( [a b c d e f g h i] ) =>
** [i b h c g e f a d]
Now, this code is not particularly efficient. Most notably, it is using pretend
calls (``i.vec'', etc) to get at vector elements; becuase there is no static
typing [*1] the checks to see that ``vec'' has an applyable type, and to call
the indexer, will take a fair amount of time. There are ways round this, which
I will not address here; I'd hope to tempt Steve [*2] to do that. It also
creates, and leaves as garbage, the vector ``vec''.
Let me know if this has been an interesting exercise for the non-Pop audience.
I'm happy to provide more snippets to illustrate the language.
----------------
[*1] The nearest thing to static typing is a ``procedure'' declaration that
arranges that procedure-valued variables can be called *quickly* (no dynamic
check needed) at the cost of checking every assignment to the variable. There
are also forms of constant declaration, if you think of those as being types.
[*2] Steve Knight, Pop hacker and efficiency guru here at Bristol.
--
Regards, | I'd say that semantically C is a ramshackle hut | Meilir
Kers. | and C++ is a two-storey ramshackle hut. | Page-Jones
|