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!

That's pretty great. (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 `.`
 == <dead>

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

Where my train of thought was going with this is that all the historical uses of / would be willing to call getters and setters (if they existed).

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 just hit a situation where I was fixing some problems with NON (inspired by mentioning it), and I was testing it with VOID.

Here's a validation that's supposed to fail (I'm asking for a non-void, but passing a void)

>> non void! void
** Script Error: :arg is VOID! (Note: use GET/ANY to GET voids)

Oops, NON hasn't been updated for the GET/ANY rule in its implementation (we decided GET-WORD! was only supposed to have one reason: get functions instead of running them)

But then... what if I had written:

>> non void! get/any 'void
== make action! [[] [...]]

VOID-the-WORD! can't evaluate to void, so VOID is a function that returns a value. It's a lot like a property access, just not done through a path. It's worth pointing out that anytime you bind in an object, you wouldn't be using a path either.

It's shaped like a case where VOID would be marked as a function that would give you :void as VOID!, but you'd need :void/ to read out the actual function? But since it's VOID itself, the :void isn't supposed to work... e.g. the only reason you were allowed to get the void is because it was running a function. But get/any 'void would give you a VOID!, while get/any/fundamental 'void would dig down to the function.

Always something to think about, here. :-/

1 Like

Have you changed Ren-C since you wrote this? With build: 3-Oct-2020/19:10:45+0:00 I get:

‌>> non void! void
** Error: NON expected value to not be VOID!, but it was

‌

The problem I mention was fixed and the powers of NON expanded by just building NON on EITHER-MATCH which is used to implement MATCH and ENSURE. I just pushed that fix.

The tiny stub NON function was about 10 lines long and has had over time about 5 stupid bugs, which speaks to the importance of not writing short fill-in functions and then not testing them.