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.