Function Names¶
Closure Nemo¶
As noted in the previous section, the profiling reports an awful lot
of closures with a name of #n
. Hmm, tricky.
It turns out you can reverse engineer which closure is which because
the first numeric column is the PC (program counter) of the closure
in question. Now you can look in vm-dasm
and search for some
clues around that PC. There’ll usually be some reference to a line
of source code which makes it fairly obvious.
Evidently, whatever it was we thought we were doing to ensure that all closures get named isn’t working terribly well. A lot of that code was relying on the VM identifying that if it was about to bind a closure value with a symbol then it should update the closure’s properties, if necessary, such that the closure’s name would be the symbol.
Clearly not working.
Part of my research for IOS included rummaging through
Swindle which includes
named-lambda
– perhaps all Scheme’s have such a
function? – somewhat incongruously associating a name with an
otherwise anonymous function.
However that’s probably the way to go. Here we might use a base
special form, function/name
, which always takes a name and have
the syntax (or keep as a special form), function
, which is now a
simple wrapper to function/name
but passes a gensym (prefixed with
anon
, say).
Evaluator¶
define¶
We can change define (foo a b) ...
to:
define foo (function/name foo (a b) ...)
ie. pass the (otherwise anonymous) function-to-be’s name as another
argument. That in turn can be passed through to the
FIX-CLOSURE
/NARY-CLOSURE
code in src/codegen.c
where a
reference to it can be added to the function construction as an extra
feature – similar features include the signature string and
documentation string. The VM can then reconstruct the name from the
reference when it sees the CREATE-CLOSURE
opcode and calls
idio_closure()
.
idio_closure()
is already creating the closure properties for the
signature string and documentation string so adding :name
to the
properties isn’t a problem – other than slowing it down further, of
course.
define-template¶
We can do something similar for define-template
noting that we
will want to do the same for the associated expander.
Body Rewrites¶
Elsewhere, the “rewrite body” and “rewrite body letrec” functions can both take advantage of similar tricks.
Where we see what will become a “local” assignment – where we extend the current function’s parameter list with some extra locals – we can use the binding symbol as a function name:
define (foo a b) {
local := function (x y) {
...
}
}
can become:
define (foo a b) {
local := function/name local (x y) {
...
}
}
Similarly, nested functions, using define
or :+
can be
reworked in a similar way.
Blocks Returning Functions¶
One area where we can’t easily get the evaluator to do the hard work for us is when a function is returning from a block, usually because the function – or functions – are closing over some local-to-the-block variables:
add-1 := {
n := 1
function (x) {
x + n
}
}
Here, it’s hard for the evaluator to reason that the block is returning a function. Even harder is if there is more than one function closing over the same variables:
add-1 := #f
sub-1 := #f
{
n := 1
add-1 = function (x) {
x + n
}
sub-1 = function (x) {
x - n
}
}
In these situations we can change the code to call function/name
directly:
add-1 := #f
sub-1 := #f
{
n := 1
add-1 = function/name add-1 (x) {
x + n
}
sub-1 = function/name sub-1 (x) {
x - n
}
}
Which isn’t Art but it’s not awful, either.
Unchanging Functions¶
There will be some places where there’s no particular interest or
(current) advantage in naming what would be an anonymous function.
For example, the one-shot arguments to map
or fold-left
or
other higher-order functions (functions taking functions).
Here, if we’re debugging, we’re probably aware that we’re in map
and that it is calling its function argument. If you’re not aware, or
fancy a change, give it a name! It will have a name already,
anon/nnn
, just not a distinguished one that is easier for
you to identify.
Unchanging Names¶
There is a peculiarity of this behaviour of associating a name with a function value – the name sticks.
Wait, that was the idea, right?
Mmm, maybe. Suppose we had a function that was going to be passed a function and a list and it was to apply the function to the list:
define (my-ph l) {
ph l
}
define (my-apply f l*) {
f l*
}
my-apply my-ph '(1 2 3)
If we debug this, then inside my-apply
we’ll be applying the
parameter f
to the parameter l*
. However, the tracer/debugger
will report that my-ph
is being applied to (1 2 3)
.
This is as much because the VM has no immediate visibility of the parameter names – only the parameter indices into the current argument frame.
However, this also rears its head if you rename functions as part of, say, increasing the complexity of a function:
define (foo a) {
...
}
foo = {
orig-foo := foo
function/name foo (a) {
...
orig-foo a
}
}
Even though we re-used the original foo
under the guise of
orig-foo
, the name associated with the function value (in its
properties) is still foo
. So you’ll have two foo
s running
with not much to distinguish them.
Last built at 2024-12-21T07:11:02Z+0000 from 463152b (dev)