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 argument

  • the 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:

src/closure.c
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.

What is setter? Really? We defined it as a primitive a moment ago.

lib/closure.idio
  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):

lib/closure.idio
  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:

lib/closure.idio
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-10-18T06:11:16Z+0000 from 463152b (dev)