Comments on: Typed Object Variables

Carl Sassenrath, CTO
REBOL Technologies
2-May-2006 16:47 GMT

Article #0016
Main page || Index || Prior Article [0015] || Next Article [0017] || 33 Comments || Send feedback

REBOL 3.0 has the ability to keep type information about the fields (variables) of an object. For example, if your object has a name, and you only want it to be a string, you can require that.

The type information for the fields is held in the new R3 context structure. It is a typeset value so you can specify multiple types, just like you can with function arguments (in fact it uses the same mechanism as functions, reducing the size of REBOL).

Question is: how best to specify object field datatypes? This is a somewhat difficult question because in REBOL object definitions are also object field initializers. You write:

make object! [
    name: "Bob"
    age: 27
    city: "Mendocino"

The object specification block is a valid REBOL block that is evaluated, and the initial values are set.

One solution might be to allow an object attribute block similar to function attributes. If the first value of the object definition is a block, it can mean something special:

make object! [
    [name: city: [string!] age: [integer!]]
    name: "Bob"
    age: 27
    city: "Mendocino"

Of course, we also want to consider what other attributes we may want to allow for objects in the future, because we don't want to add this nice attribute method and find that we cannot add other types of attributes later (such as field locking, change monitoring, etc.)

Post your comments. Thanks.



Anton Rolls
2-May-2006 13:55
You mean like this ?

make object! [ [name: city: [string! locked] age: [integer! positive :my-change-callback-func]] name: "Bob" age: 27 city: "Mendocino" ]

Anton Rolls
2-May-2006 14:06
Field locking, limiting and change monitoring can both be implemented using change callback functions, if they were implemented. Those things alone would be great for making idiot-proof user interfaces to an object.
Anton Rolls
2-May-2006 14:19
I'm thinking about unsafe code in an object - how to specify which parts of the rebol system external to the object are to be available, which are read-only and which are completely invisible and so can't be modified or evaluated by that object. Is that handled by namespaces ?
Ryan Cole
2-May-2006 14:19
:) Why not have these after the set-value, much like the layout dialect?
John Niclasen
2-May-2006 15:09
I suggest:

make object! [ name: "Bob" [string!] age: 27 [integer!] city: "Mendocino" [string!] ]

Wouldn't that work, because after each set-word, you always have one value? Even function definitions reduce to one, right? Anyway I think, my suggestion should be parse-able, but I may be wrong.

Carlo Cavallieri
2-May-2006 15:19
+1 for John Niclasen solution
Carl Sassenrath
2-May-2006 15:21
Anton: exactly. We want make sure we don't map-out such constructs (but note, lock and callback not defined at this time). We want to just open this can-of-worms very slowly.
Carl Sassenrath
2-May-2006 15:23
Anton: on visibility control, that's a function of Modules (yes, namespaces).
Carl Sassenrath
2-May-2006 15:34
Hi John, yes I considerd that option carefully. But, the method might be problematic because it relies on deep run-time information to determine the necessary information.

For example:

word: either something [this] [that] [string!]

and even more difficult:

word: my-function 1 "2" [3] [string!]

Yes, it would be possible to do... using some kind of two pass object initialization, but generally it is a good practice to avoid merging "specification" with "evaluation" for anything more complex than word definitions. (Imagine creating help functions or other reflective actions.)

Carl Sassenrath
2-May-2006 15:37
I should mention here that one of the things that I do not like about the spec-block approach is that it violates an architectural rule I have called double defining (from the old rule called "double dimensioning" in classic architectural drawing.)

In other words, you've got 2 lists of the same variables to keep up-dated. Of course, in reality, it may not be a problem, because it is likely that very few of your words would actually require special specifications (such as datatypes).

2-May-2006 16:22
I guess the question is: what do you do most often? Write code, or read it?

pros for inline info: - easy to write - information only defined once

pros for spec block - easy to parse (for reflection) - easy to add programmatically - easy to read (you see in one go, wether there are any specially defined object elements)

I'd vote for the spec block

Brian Hawley
2-May-2006 16:27
On the other hand, the spec block approach does have the advantage that USE has of letting you know your actors and constraints up front.

As for double defining, John's approach would have that problem too, or rather redundant definitions as a member word could be assigned more than once in a block processed by make. It seems like a great shortcut for something like a construct dialect though.

Still, this gives you the best advantages of struct and object both and I am all for the idea. It might make REBOL a little more compilable too.

I was wondering though, if this mechanism will be the same with functions, will that mean that when you declare type constraints on function arguments and variables, these constraints will continue to be applied throughout the execution of the function? That would mean optional typed variables - interesting. It could be a huge speedup for rebcode, provide more info for a type inferencer, and more.

2-May-2006 16:52
Another way (that I don't like at all, I just want to mention it for the sake of discussion), is to seperate definition and setting of the object. This would basically turn Rebol into a class based system:

person: make class! [ name: [string!] age: [integer!] city: [string!] address: does [print [name city]] ]

; only spec blocks and function! allowed in this step

p1: make person [ name: "Bob" age: 27 city: "Mendocino" ]

; I guess words set to function! should not be allowed to get anything else but function! values

Brian Hawley
2-May-2006 17:00
Whatever the syntax chosen, how about also including a DECLARE function that would be passed the spec block and just construct an object with those constraints and not set any values? Such an object could then be used as a prototype for others, or could be changed later.
Gregg Irwin
2-May-2006 18:37
Can you change the typeset for fields once an object is created?

Will it throw an error if you try to set a reference to a type not in the typeset, or try to coerce it first?

What is the main goal/reason for this new feature? (I can imagine uses, but I don't know what the driving force was)

Anton Rolls
3-May-2006 0:06
I don't like Ingo's class suggestion (sorry Ingo). I think it is putting the type information into too tight a box. What happens when you want to make an object where the fields have types from two or more different classes ? Carl's original suggestion is more flexible (and "aspect-oriented", if that's the right term).

Also, I don't like:

word: "hello" [string!]

Would set-words always expect a third argument then ? How uncomfortable.

3-May-2006 0:07
refinements? context[ a: none /only integer! ] (sidenote: about 'none, add it implicitely or explicitely?)
Brian Hawley
3-May-2006 1:43
Would a type constraint that doesn't include none! or unset! exclude them? If so (and I hope so), what would be the default value?
John Niclasen
3-May-2006 4:51
Another suggestion. If we would like to have the specification with the set-word, and putting it after the "evaluation" is a problem, can we put it "inside" the set-word? Something like:

make object! [ name [string!]: "Bob" age [integer!]: 27 city [string!]: "Mendocino" ]

But that's not really REBOL, is it? A new type of set-word?

John Niclasen
3-May-2006 4:58
I'm also thinking about using a new kind of SET 'command' called TYPE-SET maybe:

make object! [ type-set name [string!] "Bob" type-set age [integer!] 27 type-set city [string!] "Mendocino" ]

But that's too much to write, I think. Maybe TYPE-SET is a bad word. What about DEF?

make object! [ def name [string!] "Bob" def age [integer!] 27 def city [string!] "Mendocino" ]

Just ideas. It might be, that the spec-block approach is better.

3-May-2006 5:34
If I want to specify a type how often will it be more than one type? IMO that's an exception. So , we should have a very short way to specify ONE type.

name1: "Robert" name2:: "Robert"

name1 is like today, you are free to change type.

name2 would deduce the type implicit from the assigned value. The type will be string! in this case.

For multiple types we could use: name3:: ["Robert" Robert ["Robert"]]

3-May-2006 8:25
Is it possible to add a refinement to 'make ?

make/declare object! [ name: "Bob" age: 27 city: "Mendocino" ] [name: city: [string!] age: [integer!]]

More refinements are possible

/public /private

In my mind, the effect is exactly the same than /declare but show explicitly to the developper that theses variables are for internal use only or are intended to be read or written from outside the context of the object.


phone_number: make/public/private object! [ max-length: 20 number: "" buffer: 0 valid?: does [ either error? try [buffer: to-integer number] [ valid?: FALSE ] [ valid?: TRUE if length? number > max-length [ valid?: FALSE ] ] ] ] [ number: [string!] valid?: [logic! readonly] ] [ buffer: [integer!] max-length: [integer! locked] ]

The problems : - heavy syntax - Carl's "double defining" (easy to make a mistake) - specifications at the end (not so easy when reading code)

Another idea : Create a alternaltive 'make function wich accept another syntax.

Another (long) example:

phone_number: make_object_withspecs [ number: [string!] valid?: [logic! readonly] ] [ buffer: [integer!] max-length: [integer! locked] ] [ max-length: 20 number: "" buffer: 0 valid?: does [ either error? try [buffer: to-integer number] [ valid?: FALSE ] [ valid?: TRUE if length? number > max-length [ valid?: FALSE ] ] ] ]

USAGE: make_object_withspecs public_specs private_specs body

PS: Yes, 'make_object_withspecs is a stupid name :)

3-May-2006 9:59
Idea: all refinements in spec affect the last set-word. In spec set-words have special meaning, now refinements have too (only on top-level, not subblocks). No extra pass needed, just look for refinements while building context, or use a special do. While building contexts: Use refinements, datatypes and blocks. They are "reduce-immutable", so no need to implement some kind of skipping. With special 'do: Would be even nicer. Then you can assign a value and freeze the type later. context[ a: 123 /only ;only whats there, so here only integer! b: none /also string! ; whats there, plus other types. [none! string!] c: none /guard func[val[integer!]][0
3-May-2006 10:04
make object! [ [name: cety: [string!] age: [integer!]] name: "Bob" age: 27 city: "Mendocino" ] Spot the typo? I think for an important checkits to easy to make mistakes. Or, allow only restricted words in the spec.
Gregg Irwin
3-May-2006 12:26
What about some kind of assignment notation that is similar to == and =? for comparison? i.e. something that says "this word refers *strictly* to this type of value or, in the case of =?, that it can refer *only* to that value (like a constant, or protected word).

Just popped into my head this morning, may be a terrible idea.

3-May-2006 15:42
why not use a refinement?

make/strict [ a: "value" b: none ][ a: [string! integer!] b: [integer!]]

3-May-2006 15:48
sorry, the web editor posted before I was finished editing... arrgh!

make/strict [a [string! integer!] b [integer!]] [ a: "value" ]

note that b is none, meaning not yet set... just like for funtion local words. Do you see any similarities with another concept here? :)

as objects are evaluated, just like functions, why not use the same template we are used to? the first block declares the words, the second block sets them. Just like func and closure. consistency...

3-May-2006 16:51
Hi Maximo,

it is possible to put the argument for a refinement in front of the default arguments (with some internal fiddling), of course this way you break consistency with every other function using refinements.

3-May-2006 16:54
Hi Volker,

I spotted the typo right up (what the ... is a "cety"? :) I've thought about it, too, I guess there should be a check, that words in the spec block are _really_ used in the object! It's not perfect, but would at least find most typos.

What do you think?

3-May-2006 20:04
Why noy keep objects as they are now -- a sort of fairly freeform data structure, and add a new datatype: managed-object! (a better name will, I hope, appear).

However you define the syntax for that, keep an eye to the future when managed objects may have extra capabilities -- such as the callback for reference and modification that Anton suggests.

Petr Krenzelok
7-May-2006 8:06
:) I am for the ability to add such features, which will help minimise mistakes (allow for stronger typing) and which add to reflexibility of the language.

However, we should probably never think, that rebol datatype system is usefull for real life data. In my experience, datatypes are usefull for type checking, not value checking, and while they work for something like decimal! and integer!, they completly fail on something like email!:

>> email? to-email "nonsense" == true

I just want to say, that for higher level = application data, the best way is to probably to write custom parser (dialect) for what you expect in your data fields ... but that is probably different topic ...


Rob Lancaster
17-May-2006 5:35
:) I'd like to see a new datatype: ObjectSpec!

make objectSpec! [ name: [string! ] age: [number! ] ]

Then I'd simply be able to to determine, if an object that was passed to my function was correctly formatted by

match-spec: func[ spec [ ObjectSpec! ] object [ Object! ] ] [ ;**** Psuedo code return True if object matches spec.. ]

Various words in the body of the objectSpec! could then specify all the words! in the object had too match the spec exactly

eg: make objectSpec! [ only age: [ number! ] ]

would match make object! [ age: 10 ] , but not make object! [ age:10 name: "Peter" ]

eg: make objectSpec! [ including age: number! ] ], would match make object! p age: 10 name: "peter" ]

and don't forget...

make objectSpec [ age: [ number!] other: [] ]

For specfiying that the 'other can be any thing....

sigh: Of course I could write myself a funciton to do this kind of match using some kind of dialect, but what work! ;->

Rob Lancaster
17-May-2006 5:50
:) err...Sorry, There were a few errors in the code examples above... but the jist is there

I could also use an ObjectSpec! when defining a function..

person-spec: make objectSpec! [ only name: [string! ] ]

is-person: func [ contender [ person-spec ] ] [ true ]

This function would simply throw if an object that did not match the objectSpec! of 'person-spec was passed to it and return true if it did... ( Not that I'd actually write a function that threw on failure.... ;-> )

This approach kinda of gets round that double drafting issue/problem..

Post a Comment:

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


Blog id:



 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.

Updated 15-Apr-2024 - Edit - Copyright REBOL Technologies -