Proposal: Default Parameters - GetPoplog/Seed GitHub Wiki

Work in Progress

Outline: A feature where Pop-11 procedures can be defined with named and optional default parameters. Naming and optionality should be independent.

Implementation

The low-level design I have in mind is to implement procedures with default parameters as an enhanced closure, where each of the frozvals are named. Very roughly the concept is:

vars myfunkyproc = 
    procedure( x, optparam1, optparam2 );
    endprocedure(% 0, false %);

;;; Where frozval_name is a mechanism for associating words with frozval indexes, which 
;;; might be implemented with a weak (tmpval) identity based hash table. Or it might
;;; set pdprops to a pair and put the name in the front and a distinctive vector of names in the back.
;;; frozval_slot_name: ( int, closure ) -> word
;;; updater( frozval_slot_name ): (word, int, closure) -> ()
"optparam1" -> frozval_slot_name( 1, myfunkyproc );
"optparam2" -> frozval_slot_name( 2, myfunkyproc );

The element that makes this concept efficient, even when pop_debugging is true (which is the tricky case), is that it is relatively easy to write reasonably efficient calling code:

myfunkyproc{ optparam2 = true }( x ) becomes ...

;;; compile code for the arguments
sysPUSH( "x" );   

;;; push the frozvals onto the stack
sysPUSH( "myfunkyproc" );
sysCALL( "explode" );

;;; now check cache
lvars last_used_ref = consref( false );      ;;; last call
lvars optparam2_ref = consref( false );      ;;; slot cache for optparam2

sysPUSH( "myfunkyproc" );
sysPUSHQ( last_used_ref );
sysCALL( "fast_cont" );
sysCALL( "==" );
lvars cache_valid_label = sysNEW_LABEL();
sysIFSO( cache_valid_label );

;;; --- BEGIN - from the BEGIN to END would actually be a function call and not inlined
;;; recalculate cache
sysPUSHQ( "optparam1" );
sysPUSH( "myfunkyproc" );
sysCALL( "frozval_slot_index" );
sysPUSHQ( optparam2_ref );
sysUCALL( "fast_cont" );

;;; and reset as clean
sysPUSH( "myfunkyproc" );
sysPUSHQ( last_used_ref );
sysUCALL( "fast_cont" );
;;; --- END

sysLABEL( cache_valid_label );

;;; and now update the stack entries
;;; calculate the RHS of the optional parameter
sysPUSH( "true" );                         ;;; NB. We must check this yields one and only one value
;;; and move to the right place
sysPUSHQ( optparam2_ref );
sysCALL( "fast_cont" );
sysUCALL( "subscr_stack );

;;; Finally invoke the underlying procedure
sysPUSH( "myfunkyproc" );
sysCALL( "pdpart" );
sysCALLS( undef );

When pop_debugging is false we can do very much better than this because we can hard-code the indexes.

Alternative Syntax Designs

Design A

Question: could you do a syntax like fn(x, y)[z] ? As far as my personal taste goes, I'd prefer terser syntax like that.

Yes, this is a minor variation on the following design, although I put the optional parameters first & used curly brackets.

define myfunkyproc{ optparam1 = 0, optparam2 = false }( x );
    ... you can use optparam1 and optparam2 in here ...
enddefine;

Observation: The curly brackets vs square brackets is a free choice, although it might be wise to reserve square brackets for indexing. Comment: I don't really care that much about the bracket style, square braces are somewhat preferable given they don't require the shift key

Observation: Putting the default arguments first or second is a bigger decision, although both are available. Comment: I've no strong opinions on that

Why bigger? Because you want to be able to abstract over closures without calling them. This is a very distinctive point about Pop-11.

So let's suppose we want to map myfunkyproc over a list - but with optparam2 being true. A Pop-11 programmer would expect to be able to write ...

maplist( [ 1 2 3 ], myfunkyproc{ optparam2 = true } )

And you also probably want to integrate with the . syntax.

99.myfunkyproc{ optparam2 = true }

There is a slight gotcha for when the default parameters go after the main parameters.

If we assume that the optional parameters follow the mandatory args but we also allow f{ y = E } then it is ambiguous what f(x){ y = a } means.

( f(x) ){ y = a }  -vs-   f(x, y = a)

The second form I am just stealing from Python

Design B

The cheatsheet summary of the named parameters is:

with
    optparam1 = 0,
    optparam2 = false
define myfunkyproc();
    ... code can refer to optparam1 & 2 ...
enddefine;

And

with optparam2 = true do myfunkyproc();

Comment: It's quite verbose for something that I'd imagine I'd use frequently

with optparam2 = true do myfunkyproc();
myfunkyproc( optparam2 = true );

Comment: it's quite hard to change a mandatory to an optional though

Changing the mandatory parameters of a procedure is one of the high risk transformation in Pop-11 'cos of the open stack.

Comment: It also goes against the standard syntax of arguments being within parens after the function name Response: And yes, it does go against that common usage. I can't fight that one! (edited) So to change a mandatory parameter to optional:

define erase_it( x );
enddefine;

Becomes

with
    x = 'wut?!'
define erase_it();
enddefine;

Design C

There is, of course, a fairly obvious approach which is:

define myfunkyprop( x | optparam1 = 0, optparam2 = false );
   ... optparam1 & optparam2 available here ...
enddefine;

i.e. we grab some random separator (e.g. "|") and use that to divide the two.

myfunkyparam( 99 | optparam2 = true )

This is visually neat and unlikely to screw up many existing programs

The catch is that it doesn't work with . syntax and also offers no easy way to freeze in new optional values. So I rejected that approach, reluctantly.

Design D

And of course there is the Python envy approach, where we just allow default arguments like this:

define myfunkyprop( x, optparam1 = 0, optparam2 = false );
   ... optparam1 & optparam2 available here ...
enddefine;

And

myfunkyproc( x, optparam2 = true )

The catch is that function applications require hijacking the ( symbol, which is a complex bit of code, and used pervasively. This means any enhancement has to be written extremely accurately. It also requires expression movement, which isn't robust in the face of badly written macros. This combination makes for a technical challenge I don't fancy.

It also does not combine with the . operator, nor does it lend itself to abstraction and it also has the problem that part-apply brackets myfunkyproc(% optparam2 = true %) also needs overriding.

The likelihood of this being a success I rate as nil. So I didn't consider that very much.