REBOL 3.0

Comments on: The concept of Set-Functions

Carl Sassenrath, CTO
REBOL Technologies
2-May-2006 21:02 GMT

Article #0019
Main page || Index || Prior Article [0018] || Next Article [0020] || 21 Comments || Send feedback

You know what a set-word is:

word: 10

It's the syntax REBOL uses to define a word's value.

A set-function is function that gets called when that set action take place. A set-function for the above example might check the range, datatype, or keep track of other details.

The concept of a set-function is not new. In fact, it's an essential part of one of REBOL's grandparent languages: Self. (See Self: The Power of Simplicity as one of my favorites.)

Back in the early formative days of Self while I was at Apple, I use to hang out with David Ungar (Self's designer, at Stanford at the time) and talk language talk. I found it interesting that David wanted to eliminate state from Self by removing assignment. (Assignment is one of the classical headaches in pure functional languages.) He accomplished that by always using functions for both getting and setting values within a context (object).

The idea always appealed to me, but I felt it wasn't exactly practical. State is macro-component of nature (I did not say "component" here because I do not want to enter an endlessly deep discussion.) Elimination of state is a mathematical pursuit, but not necessarily practical. There are many useful features of state. A newspaper is very practical, but mathematically disfunctional.

When it came time to consider this approach in REBOL (more than a decade ago), I decided to keep it practical. However, secretly, I've always wanted to bring back the set-function method because it does offer great value. A set-function can precisely control how a word is set and what it is set to.

I should mention, however, it's not that easy to add set-functions. If it were really easy, we'd have them. The problem is that set-functions add another branch to the tree of REBOL language semantics. For example, how are set functions defined and how reflective are they?

Also, you can "sort of" do set functions today with code like this:

obj: make object! [
    value: 0  ; internal
    set-value: func [new] [value: new]
]

And you would write:

obj/set-value 10

But, what you really want to write is:

obj/value: 10

where it passes through a function to do the set.

But then the question becomes, how do you refer to the set-function itself?

Ok, so that's something to think about. I'm not sure there is a good answer, or even if we need one. I've talked about this before, and not even suggesting that this be added to REBOL 3.0 (see prior article about cool language tricks).

I suppose I'm just trying to get you thinking more about it... Please post your comments.

21 Comments

Comments:

Brian Hawley
2-May-2006 17:43
This seems like properties in component-based languages like Delphi and all of its decendents (JavaBeans, .NET languages, etc.). You could put get and set attributes in the object spec you mentioned earlier today as additional attributes.
Brian Hawley
2-May-2006 17:50
This feature would also make it easier to use Windows' ActiveX and .NET facilities because there wouldn't be as much of a semantic mismatch. As more of Windows' functionality is being moved to these platforms this would make REBOL a lot more effective there.

You might not want to put this off.

Steeve
2-May-2006 19:41
today if i want simulate a simple set/get function in rebol, i do this:

a: make object! [ internal: none self: func [/set data][ either set [internal: data][internal] ] ]

>> a/set 5 == 5 >> a ==5

So, the problem is to allow a special refinement in functions, so that we could write [a:] instead of [a/set] A second problem is to let rebol recognise 'a:' as the path of function and not as a set-word.

Am I wrong ?

Brian Hawley
3-May-2006 0:17
As for how to access the property get and set functions, I recall another language using functions named "getter" and "setter" to retrieve the relevant functions. Perhaps something similar would work for REBOL. As long as it isn't another weirdly overloaded ordinal it should be fine.
Volker
3-May-2006 0:55
How about some proxy-object?

get-set: make proxy![ get: func[object word][ object/:word ] set: func[object word value][ either value < 5 [ object/:word: value ][ make error! ".." ] ] ] b: get-set b: 3 ; works b: 7 ; error

Note: to save memory object and word are passed as argument, so one 'get-set can proxy for many words.

Anton Rolls
3-May-2006 1:53
var: make object! [ ; attribute spec block [ self [ ; rebol creates an internal "proxy/access" object, with these functions in it value: none get: func [][value] set: func [new][value: new] ; return the value that self should appear to be ] ]

;

Anton Rolls
3-May-2006 1:54
(Let me try that again)

var: make object! [ ; attribute spec block [ self [ ; rebol creates an internal "proxy/access" object, with these functions in it value: none get: func [][value] set: func [new][value: new] ; return the value that self should appear to be ] ]

;

Anton Rolls
3-May-2006 1:55
(damn haloscan)

var: make object! [ ; attribute spec block [ self [ ; rebol creates an internal "proxy/access" object, with these functions in it value: none get: func [][value] set: func [new][value: new] ; return the value that self should appear to be ] ]

; more object specs can go here as usual... ]

var: 1 ; == 1 :var ; == 1

So now VAR looks and feels to the rest of rebol just like a normal word. So to get the object that actually implements it, I propose:

::var ; new super-get-word! returns the object var:: 1 ; new super-set-word! (in this case sets VAR back to a normal integer)

I think it's the only way. If we put an abstraction between rebol and normal values, then we need these extra datatypes to access it.

A mezzanine could do the work of creating the messy-looking attribute spec block above:

var: make-abstract-value [ value: none get: func [][value] set: func [new][value: new] ; return the value that self should appear to be ]

Gregg Irwin
3-May-2006 2:42
Syntax is important, no doubt, but I don't think we should focus on the syntax first, not the low-level syntax anyway. Let's look at it from the top down.

For example, not many people use the view subsystem or faces directly; they use VID. Do we think people should be writing access functions directly (and isn't that just playing "catch up" with other languages that use this model)?

This ties into blog 0016 (typed object variables) as well. How do we really *want* to define and handle constraints like this? What about a dialect (or more than one) for defining objects? Once we have these constraints "available", how will we use them reflectively, maybe from a GUI, maybe via reb services?

Should it work like face accessors in VID, so you can create and share the definitions easily? Why not just have a function that can return the set-function, rather than creating a new lexical form? When you serialize an object with all this extra information, what should it look like?

There are a *lot* of big questions here beyond syntax, IMO, though the syntax will be very important.

Gregg Irwin
3-May-2006 2:47
It also ties into debugging. How do you step trace things, set watchpoints, etc.? If you're into AOP, DbC, or TDD/xUnit, how do you annotate things for aspect weaving and determine when to apply/evaluate contract conditions; if we want a built-in test dialect, will it be affected, and how?
Gabriele
3-May-2006 4:52
:) Personally, I don't miss set-functions much. They're only useful to write "cool" code; in practice there's no real advantage except for saving a few keystrokes.

But, I won't oppose them.

Robert
3-May-2006 5:50
The concept is very nice, as it brings the "check" near the point where it happens. And a word and it's set-function are one unit.

On the other hand, that could be the biggest disadvantage. What if you build up an object from several words, each using a set-function to check the input format of the user? Now you have all those checks scuttered around.

I would prefer one "state machine" that handles all this and have words that store stuff.

Syntax:

name: "Robert" name/set: func [...][....] name/get: func [...][....]

Using a word! refinement.

Volker
3-May-2006 9:32
Rethinking - do we want accessors, like get/set? Or is it only guards? Then we need only 'set. That would make sense, because it prevents to destroy important things. Reading something wrong is a local bug. Writing something wrong can crash the system.

And if one really want get/set, well, put the getter in the word itself.

Language-semantics: How about "fourth object"? first is words, second values, third in use, fourth set-funcs? For initialisation use whatever you choose for typing and allow a function there. http://www.rebol.net/cgi-bin/r3b...log.r? view=0019

About "What if you build up an object from several words, each using a set-function to check the input format of the user? Now you have all those checks scuttered around." I think that is a job for a dialect, keeping multiple changes in sync. But still, you can have a 'verify-state which is called by every set-func.

Volker
3-May-2006 9:35
Wrong link, should be http://www.rebol.net/cgi-bin/r3b...log.r? view=0016 , "Typed Object Variables". Because if its about guards, that is related.
Anton Rolls
3-May-2006 12:07
An example where I would use set-functions: defining a smooth public interface for VID styles. Eg. my scroll-area style combines an area and two scrollers, so the face hierarchy looks like:

scroll-area area scroller ; vertical scroller ; horizontal

The area and the two scrollers are inside the pane of the scroll-area face which is just being used as a container. But this makes a more complicated user-interface. For a layout like this:

layout [scra: scroll-area]

Where the user expects to be able to do this:

scra/text

to access the text we actually have to do this:

scra/area/text

and the same for other facets like size etc. It would be good if changes to the containing face size would cause the subfaces to be resized automatically.

As previously discussed, the new Rebol3 architecture may have broken up the face into smaller parts, probably making it easier to create such styles with a smoother user interface.

maximo
3-May-2006 16:58
for anyone building apis, this is very usefull, as it allows your new contructs to play WITH the rest of the language directly... something NOT possible with dialects.

setting and getting, might need to do more than just set and get the value, maybe logging, maybe the data must be represented differently, might even call triggers...

IMHO, adding user datatypes, could solve this issue and clearly add something more than just "a trick" to the language.

Ged Byrne
4-May-2006 10:17
:) Betrand Meyer has come up with an elegant approach in the latest version of Eiffel.

http://www.eiffel.com/general/ mo...pt_October.html

Brian Hawley
4-May-2006 15:19
Volker, I like your idea of implementing getters by just assigning a function to the field. REBOL-like simplicity!
Brian Hawley
4-May-2006 17:49
How about adding a property! type to the field's typeset and using set-property and get-property natives to set and get, respectively the setter and getter of the field. You could specify this using refinements, a word parameter (like get-modes) or a dialect (like read/custom).

You could even make the property! type like a special object type, where a word "value" (like self in object) could refer to an internal field that would store the value and could only be accessed directly by the getter and setter. Like this:

a: make object! [ [blah: [property!]] blah: property [ get [value] ; params like for does set [x [integer!]] [value: x * 2] ; params like for func, one arg only ] ] ; Access functions, there are likely better names getter 'a/blah ; == #[function! [][value]] setter 'a/blah ; == #[function! [x [integer!]][value: x * 2]]

Brian Hawley
4-May-2006 17:57
In my last example I forgot initialization. The property dialect should add initialization of the value, like this:

property [ value: 0 ; initial value get ... set ... ]

Then, perhaps, you won't need the internal words to be predefined - they can be specified as set-words like they are with construct (not full execution like with make object!).

Brian Hawley
4-May-2006 18:38
Maybe the property! type's internal object could be a regular context, like an object! prototype with words for each property access type predefined. This could be four operations, for direct and indexed get and set.

A structure like this (pseudocode):

property!: make object! [get: set: get-at: set-at: none] property: func [spec [block! object! property!]] [make property! spec]

It could be used like this:

blah: property [ value: 0 get: does [value] set: func [x [integer!]] [value: x * 2] ]

blah2: property [ value: copy [] get-at: func [i [integer!]] [value/:i] set-at: func [i [integer!] x [integer!]] [value/:i: x * 2] ]

Any property operation that is undefined or none at runtime wouldn't be allowed. You could make read-only or write-only properties by leaving set or get as none. If get-at or set-at are defined, indexed access would be allowed.

Does this sound usable?

Post a Comment:

You can post a comment here. Keep it on-topic.

Name:

Blog id:

R3-0019


Comment:


 Note: HTML tags allowed for: b i u li ol ul font span div a p br pre tt blockquote
 
 

This is a technical blog related to the above topic. We reserve the right to remove comments that are off-topic, irrelevant links, advertisements, spams, personal attacks, politics, religion, etc.

REBOL 3.0
Updated 25-Apr-2024 - Edit - Copyright REBOL Technologies - REBOL.net