Imagine for a moment that Scheme bindings could bind, not just values but all sorts of properties that a field might have. Think CLOS. Here are some of the possibilities:
(let-plus name ( ;;Equivalent to an ordinary binding (id1 'value value-1) ;;An immutable object (id2 'value value-2 'setter #f) ;;An immutable object with a getter. (id3 'value value-3 'getter get-id3 'setter #f) ;;A "called" object, with both getter and setter. (id4 'value value-4 'getter get-id4 'setter set-id4) ;;An uninitialized "called" object (id5 'getter get-id3 'setter set-id5) ;;A type-checked object. (id6 'value value-6 'satisfies my-type-pred?)) (list id1 id2 id3 id4 id5 id6) (set! id1 12) (set! id2 12) (set! id3 12) (set! id4 12) (set! id5 12) (set! id6 12))
(let name ( (id1 value-1) (id2 value-2) (id3 value-3) (id4 value-4) id5 (id6 value-6)) (list id1 id2 (get-id3 id3) (get-id4 id4) (error "Uninitialized id5") id6) (set! id1 12) (error "Can't set id2") (error "Can't set id3") (set! id4 (set-id4 12)) (set! id5 (set-id5 12)) (let ((new-val 12)) (if (my-type-pred? new-val) (set! id6 new-val) (error "Wrong type"))))
What this provides
This mechanism could provide:
- immutable fields
- effectively constant environments
- enforce type restrictions
- succintly enforce controlled access to fields (They can be succintly provided now, but not succintly enforced)
How this can be minimal and extensible
Looking at the above, you can see that there are already a fair number of fields that might be set, and one would like for the user to can extend it further. Surely we don't want to build anything so complex into the core language.
So what is a modest construct that can support this? A mere syntactic transformation won't do. It won't apply this everywhere the current environment is used, which we need.
So I propose a primitive binding construct that knows a getter function. That is, when the associated symbol is evaluated, the value is the return value of the getter function when passed the (actual) value associated with that symbol.
In SRFI-17, any procedure can have a setter, which is an associated procedure that sets the object's value and set! will use it, so controlled setters would just fall out.
(controlled-let my-guard ((a 12)(b 144)) (list a b))
is equivalent to:
(let ((a 12)(b 144)) (list (my-guard a) (my-guard b)))
In order to implement the generalized binding constructs on top of this, one would:
- Predetermine a suitable object type T
Predetermine a getter that expected it argument to be of type T
- Similarly for the setter, if provided.
Write a macro that:
- Takes a list of generalized binding specs and a body
- Interpret each binding spec as a symbol plus the arguments to a constructor for an object of type T.
Uses controlled-let, giving it:
- the predetermined getter
- The zip of the bound symbols and the list of constructor forms
- the body argument