Setters¶
Setters comes from the idea of a Scheme generalised set!
– described in SRFI-17.
I’ve not followed that SRFI directly but rustled up something along the same lines.
The Problem¶
The basic question is: how do I set!
(assign to!) something that
isn’t a simple variable?
An even more basic question might be why do you want to assign to such a thing? Look, it happens!
In fact, we’re going to make it happen. We allow the idea of
accessing some indexable value, like an array, using the .
or
value-index
operator: a.i
.
That is translated into value-index a i
and
value-index
will test whether a
is an array or hash
table or structure instance or a list and then try to use i
to access an element of it.
Cool. Not exactly efficient but cool.
Now, what happens when we want to assign to that indexed value?
Sounds innocent enough. a.i = 3
is going to see a couple
of transformations into:
set! (value-index a i) 3
Ooh eck! That looks like we’re trying to assign to the result of a function call! That’s not going to end well.
SRFI-17 has a neat solution. We won’t assign to the result of a function call – because that really is madness – but rather we’ll make a subtle transformation:
set! (foo ...) expr
will be transformed into:
(setter foo) ... expr
Eh? We’ll separate out the function call name – this will only work for named functions – and the arguments to that function call then we’ll construct a replacement statement which has:
a call to a function called
setter
with the function call name as an argumentthe original arguments to the original function call
the expression we want assigned
setter
’s job is to find out what the “setter” of a given function
is. So (setter foo)
is asking for the “setter of
foo
”, ie. the function that is capable of “setting”
something when we’re ostensibly making a call to foo
.
Let’s divert with a quick example. Suppose I have a pair, p
, and
I’ve created the following:
set! (ph p) 3
which we now know is going to be transformed into:
(setter ph) p 3
what “setter of ph
” can possibly solve that conundrum? Well,
let’s try… *shuffles pack* … set-ph!
. Let’s suppose that
(setter ph)
returns set-ph!
(or the value of, anyway). Let’s
try that:
set-ph! p 3
Why, wouldn’t you just know it? That works! That is exactly what
we want: set-ph!
expects two arguments, a pair and a value, and
then will make the head of the pair refer to the value.
OK, now that we know it’s possible, we simply need to set up a nice
table of setters. How do I “set the setter of foo
”? This
is looking a bit gnarly but if we just lay it out it’ll be:
set! (setter foo) setter-of-foo-func
Which we’ve just suggested is setting the result of a function call for which we know the formula:
(setter setter) foo setter-of-foo-func
Almost! What this is saying is that setter
needs a setter whose
job is to set the setter of its first argument.
If we can bootstrap everything with a “setter of setter” then the rest just falls into place like magic.
Implementation¶
The implementation uses what might be called property lists but are
little hash tables associated with a value. So there is a big
“properties” table, indexed by values, which gives you a little hash
table of per-value properties, indexed by some keywords. An obvious
keyword, here, is :setter
. In C that becomes
idio_KW_setter
– where KW
is for, uh, “keyword.”
We need a hook to get us going. That’s going to be a primitive called
setter
whose job is to return the :setter
property for some
procedure, p
– I’m using “procedure” to mean either a primitive
or a closure:
IDIO_DEFINE_PRIMITIVE1_DS ("setter", setter, (IDIO p), ...)
{
...
IDIO setter = idio_get_property (p, idio_KW_setter, IDIO_LIST1 (idio_S_false));
if (idio_S_false == setter) {
idio_error_C ("no setter defined", p, IDIO_C_FUNC_LOCATION ());
}
return setter;
}
Reasonably straightforward. The fun is in lib/closure.idio
.
In the first instance, we’ll ask for the “keyword table”,
setter-kwt
, for setter
itself and create one if it doesn’t
exist.
Weren’t you paying attention?
What is setter
? Really? We defined it as a primitive a moment
ago.
setter-kwt := %properties setter
if (null? setter-kwt) {
setter-kwt = make-keyword-table 4
%set-properties! setter setter-kwt
}
(We would expect the properties table for setter
to not exist
but it might. So we’re just covering bases, here.)
Now the interesting bit. The “setter of setter” is a function that is
going to be called with two arguments: a procedure, p
, and a
setter, s
, for that procedure so our “setter of setter” is going
to look like: function (p s) ...
.
This function’s job is then to dig out the keyword table for p
and
assign s
to the :setter
keyword in that table. Something
like:
function (p s) {
p-kwt := %properties p
if (null? p-kwt) {
p-kwt = make-keyword-table 4
%set-properties! p p-kwt
}
keyword-set! p-kwt :setter s
}
Of course, this function that we’re defining wants to be set as the
:setter
in the setter-kwt
table – thus bootstrapping the
whole shenanigans.
In the Scheme-ish way, we’ll do it all in one (and use
confusing names in place of p
and s
):
keyword-set! setter-kwt :setter (function (proc setter) {
proc-kwt := %properties proc
if (null? proc-kwt) {
proc-kwt = make-keyword-table 4
%set-properties! proc proc-kwt
}
keyword-set! proc-kwt :setter setter
})
Not quite done yet. Let’s fill in some standard setters:
set! (setter ph) set-ph!
set! (setter pt) set-pt!
set! (setter array-ref) array-set!
set! (setter hash-ref) hash-set!
set! (setter string-ref) string-set!
set! (setter struct-instance-ref) struct-instance-set!
set! (setter value-index) set-value-index!
These statements work because in the assignment special form behaviour
code we look out for the “name” that we are setting being a list and
immediately rewrite the list as (setter (ph name)) (pt name)
expr
– exactly as described above.
We have created the bootstrap “setter of setter” just before
defining these standard setters so that function is invoked to
define the “setter of ph
” to be set-ph!
, the “setter of
pt
” to be set-pt!
etc..
Last built at 2024-12-21T07:11:00Z+0000 from 463152b (dev)