From Liability to Asset: WORD! and PATH! always running code

Since there are no parentheses in function application, Rebol winds up with the somewhat sketchy aspect that you can't tell when looking at a WORD! or PATH! if it's going to give you a value or run arbitrary code.

Tonight I've reached a point in the generic tuple + path change such that /foo, .foo, foo., and foo/ are all forms of PATH! and TUPLE! that fit efficiently into a single cell. But once things like foo. existed I had to make them do something... so I went ahead with trying my suggestion:

>> foo: 10
== 10

>> foo.
== 10

>> foo/
** Error: Terminal slash in PATH! did not follow ACTION!

>> append/ [1 2 3] 4
== [1 2 3 4]

>> append.
** Error: Picking from action on left of dot in TUPLE!

Kinda neat. (Error messages need some thought). And I noticed something else neat about terminal slash...which is that it makes a good shortcut for REEVAL to get a function value then force its execution:

>> (specialize 'add [value2: 10]) 20
== 20  ; GROUP! evaluated to an ACTION! and was discarded

>> reeval (specialize 'add [value2: 10]) 20
== 30  ; this is how you previously did it without a new variable for the action

>> (specialize 'add [value2: 10])/ 20
== 30  ; shorthand!

But Is There More Untapped Power Lurking Here?

My first thought went to protecting from Rebol's dangerous "always execute IF a function" semantics.

But being able to make member access look the same when calling a function or not is a sought-after ability in other languages. Most discussions of the feature involve "Property Getters and Setters"...where you can override non-parentheses access and assignment for a field. For instance, in C#:

"Properties combine aspects of both fields and methods. To the user of an object, a property appears to be a field, accessing the property requires the same syntax. To the implementer of a class, a property is one or two code blocks, representing a get accessor and/or a set accessor"

By its nature, Rebol has "getters" because you don't need parentheses to get an object member. Just make a function with no arguments:

>> my-object: make object! [
       x-private: 10
       x: func [] [
            print "Getting X!"
            return x-private + 20
       ]
 ]
            
>> my-object.x
Getting X!
== 30

But it doesn't have overrideable behavior for SET-PATH! or SET-WORD!, so you can't make something like my-object.x: 10 call code. The X function just gets overridden.

The little inkling of a thought I had was that we might be able to use the PATH!/TUPLE! distinction somehow to tell the difference between calling the setter/getter function and dealing with the function itself.

 >> foo.x: 1000 
 Calling the Setter Function!
 == 1000  ; imagine setter stores value - 20

 >> foo.x-private
 == 980  ; ...so it updated the private value

 >> foo.x
 Getting X
 == 1000 

 >> foo.x.: <dead>  ; notice the terminal `.`
 == <dead>

 >> foo.x
 == <dead>   ; no setter or getter anymore

It's just an inkling at this point of something that might be interesting. I was kind of lamenting how many / there are in the codebase, when it seemed to me I'd be wanting to use . almost always. But that got me to wondering about the kinds of motivations that people would have for using one form vs. the other. Maybe this plays into it.

All that aside... really just kind of announcing that generic tuple and path unification with compression to single cells is coming up shortly. There are a few "big" issues to think about, but the mechanics are working...for the most part.

1 Like

Wicked! This will likely be viewed by some as adding noise/punctuation to the otherwise clean language, but I like it.

Even if you take advantage of cool new brevity-enhancing things like NON, you still need a GET-WORD! to avoid ACTION! invocation:

append x non action! :a

Unless you go for an apostrophe, I guess, but then you need a GET:

append x non action! get 'a

By comparison, taking advantage of terminal dots is cleaner and I think quite learnable:

append x a.

Of course append x a is "cleaner". And people don't have to use protective techniques in their scripts if they "just know" things aren't actions. But the problem with writing mezzanines or libraries is you have less control over what you're passed. You can type check a function's arguments, but once you pick values out of objects/etc. all bets are off unless you have some validation.

As for overriding the SET with lower-level vs. higher level, that could be a refinement:

obj.x.: <dead>

set/fundamental 'obj.x <dead>

In all these cases, people can decide what they prefer. The bias on this is to historical "it does whatever" which is compatible, and also good for code golf. But now getting some validation is cheap and visually fine enough that I don't mind doing it so much when I need it.

In other observations: there doesn't need to be separate setter and getter functions, as they can use the <end> status:

 foo: make object! [
     x-private: 10

     x: func [arg [<end> integer!]] [
         if arg [
             print "Setting!"
             x-private: arg - 20
             return arg  ; !!! do all setters enforce returning the value?
         ] else [
             print "Getting!"
             x-private + 20
         ]
     ]

     mark-property 'x  ; lame interface, but this has to be known somehow
 ]

The question comes down to how to mark these functions as "setters", or "properties", or whatever.

Path access is due for a reckoning, so having the TUPLE!/PATH! distinction to mess with is a good motivator.

1 Like

I found another case where this would have come in handy...

Some code was written to access global variables in Query, that I moved into an object.

To change less code, I thought about just making a shorthand to pick the member out of the object. It would only be able to read, but most usages were reads. You could even PROTECT it, to stop you from overwriting the helper. Like this:

sc: does [qe.sc], protect 'sc

The hope was that existing references to the global as just sc would continue to work.

It works for retrieving the object, but not selecting fields out of it.

>> sc
== make object! [...]

>> sc.field
** Script Error: sc is ~#[action! {sc} []]~ isotope (see ^(...) and GET/ANY)

The tuple reference is trying to pick FIELD out of the function itself, not out of the result of the function. You have to write (sc).field

If SC had some magic to make it a property getter/setter, then both assignments and references could be made to work. It's worth thinking about...

1 Like