A first tentative step showed some success, and allowed the deletion of ugly cruft while still working on complex examples! That's always a win.
But when you're writing freeform code inside of a MAKE OBJECT!, it can be easy to forget to use .field
instead of just field
. This is because at time of writing, everything in the object spec block sees the words of the object being created.
e.g. if you say:
obj: make object! [
x: 10
repeat 3 [
x: x + 1
print ["x is" x]
]
]
That will give you:
x is 11
x is 12
x is 13
== make object! [
x: 13
]
The broad and deep availability of the object's fields inside of this can be very cool sometimes. But a lot of the time it is a pain... you often only want the top-level SET-WORD!s to target the object being created, leaving plain WORD!s and anything deeper alone:
make-point: func [x [integer!] y [integer!]] [
return make object! [x: x, y: y] ; can't do this, x and y are unset!
]
make-point: func [x [integer!] y [integer!]] [
return make object! compose [x: (x), y: (y)] ; works, but ... annoying
]
So I'm gathering we need a stricter construction operation that doesn't deeply fog up the bindings inside the block. Essentially, semantics like this, where all the code that isn't a top-level SET-WORD! acts like it isn't inside the object's "scope" at all:
obj: simple-construct [x: <expr1> y: <expr2> z:<expr3>]
=>
obj: make object! [x: y: z: null]
obj.x: <expr1>
obj.y: <expr2>
obj.z: <expr3>
This way, if you define a function inside a SIMPLE-CONSTRUCT you can't fail to use the .field
reference style for the members.
I've pointed out before that what MAKE OBJECT! does is high-level and weird, and needs to be broken up into component operations. And I'm now suspecting that FENCE! should have the SIMPLE-CONSTRUCT semantics, such that the following would work:
make-point: func [x [integer!] y [integer!]] [
return {x: x, y: y}
]
Not that doing-as-JavaScript-does is much of an argument for anything, but I'll point out that does work in JS:
function make_point(x,y) { return {x: x, y: y} }
>> make_point(10,20)
<- Object { x: 10, y: 20 }
Looking back on history, I actually see the motivation for why CONTEXT used to be a synonym for what MAKE OBJECT! does in Rebol2/R3-Alpha-Red:
context: func [
"Defines a unique (underived) object."
blk [block!] "Object variables and values."
][
make object! blk
]
If you write something like:
obj: context [foo: 10, print ["foo is" (foo: foo + 1)], bar: 20]
That actually makes clearer sense, in that it tells you what you're doing is setting up a context in which code will run. I didn't like it because CONTEXT was a noun. Maybe CONTEXTUALIZE?
obj: contextualize [foo: 10, print ["foo is" (foo: foo + 1)], bar: 20]
But it makes sense that there's a verb in this variation that takes in a BLOCK!, because there are high odds you will need to COMPOSE it if there are any collisions between names in the context and names outside of it that you want to reference.
Perhaps we should start looking at MAKE as being a truly high-level builder, not a low level one. I have had some thoughts about that, even going back to variadicness *(via Ren-C mechanisms), perhaps like:
foo: make function [arg] [print ["arg is" arg]]
obj: make object [foo: 10, print ["foo is" (foo: foo + 1)], bar: 20]
MAKE could quote the word after it, and this doesn't look too bad. You could still specialize the non-variadic bits today:
func: specialize :make [name: 'function]
Partially specializing the variadic portion would be less easy, so perhaps there would be make-function and make-object available, which are dispatched to by the internals of MAKE's syntax sugar.
Tangents aside though--this does look like some of the haze surrounding this may be dissipating. If coming up with names for things winds up being the last bit to solve, we'll be quite lucky.