Code Planting Tutorial Part 2 - GetPoplog/Seed GitHub Wiki
Step 2: Calling Functions
For this tutorial it helps to know that:
- The built-in function
applist( LIST, PROC )
will apply the procedure PROC to each element of LIST in turn, much likeforEach
does in other languages. - There are two ways to write function calls. Both
f(x)
andx.f
mean the same in Pop-11 i.e..
is a postfix function-apply. - Part-apply is written like this
f(% x, y %)
and it creates a closure that will pushx
, pushy
and then callf
.
The objective
In this tutorial we build a procedure that calls a series of variables, one after the other. This is function composition and a generalisation of the built-in function pdcomp. As input we will take a list of variable names. For example, we will implement this:
;;; The variable -foobar- is bound to a procedure that calls the variable -foo- and then the variable -bar-.
vars foobar = composeAll( [ foo bar ] );
Because arguments are passed and results returned via the stack there is no need to implement any 'plumbing' code, which means that all we need to do is call each variable in turn. This time we will start with the boilerplate code for writing a code planting function. Just to mix things up we will use the postfix .
operator to invoke sysCOMPILE
in idiomatic style.
define composeAll( varlist ); lvars varlist;
procedure();
sysPROCEDURE( false, 0 );
.... the main code goes here ...
sysPUSHQ( sysENDPROCEDURE() );
sysEXECUTE()
endprocedure.sysCOMPILE
enddefine;
The key code-planting function that we need is sysCALL(wordarg)
. The wordarg part is typically just a word and that's all we need for this example. The core of our code will iterate over the varlist and sysCALL each in turn. We will use the built-in applist
procedure for applying a procedure to each member of a list in turn. So all we need is this:
applist( varlist, sysCALL );
Putting this together we get:
define composeAll( varlist ); lvars varlist;
procedure();
sysPROCEDURE( false, 0 );
applist( varlist, sysCALL );
sysPUSHQ( sysENDPROCEDURE() );
sysEXECUTE()
endprocedure.sysCOMPILE
enddefine;
It is quite unsatisfactory that the boilerplate code outweighs the key code. So we should indulge in a little refactoring.
define buildNewProcedure( code_planter ); lvars procedure code_planter;
procedure();
sysPROCEDURE( false, 0 );
code_planter();
sysPUSHQ( sysENDPROCEDURE() );
sysEXECUTE()
endprocedure.sysCOMPILE
enddefine;
define composeAll( varlist ); lvars varlist;
;;; We use partial-application to freeze in the arguments.
applist(% varlist, sysCALL %).buildNewProcedure
enddefine;
Mixing in procedure values
We can make our code a good deal more useful by allowing our list of words to be a list of words or code-planting procedures. We're going to need this capability in our headline example. Here's how that looks:
define composeAll( varlist ); lvars varlist;
applist(%
varlist,
procedure( x ); lvars x;
if x.isprocedure then apply else sysCALL endif( x )
endprocedure
%).buildNewProcedure
enddefine;
Trying out our code
So let's try using our new -composeAll- to create a function that takes a list and appends a reversed copy to create a palindromic list. Of course we could write this by hand:
define makePalindrome(L); lvars L;
lvars R = L.rev;
return( L <> R )
enddefine;
To do this we are going to want to plant code that looks a bit like this pseudo-assembler:
CALL duplicate
CALL rev
CALL <>
Unfortunately there's no duplicate built-in in Pop-11. But there is a neat code-planting procedure called sysPUSHS(dummy)
that plants code to duplicate the top item of the stack; the dummy argument is discarded and only there for regularity with similar procedures. So instead we will plant this:
PUSHS false
CALL rev
CALL <>
With our composeAll
function this is now quite trivial:
: vars procedure makePalindrome = composeAll( sysPUSHS(%undef%) :: [ rev <> ] );
: makePalindrome( [ eat my shorts ] ) =>
** [eat my shorts shorts my eat]
: