TUPLE! and PATH! Big Picture Concept

In the past I've suggested the idea that TUPLE! run on the basic same schematic as paths.

a.b.c  ; a TUPLE!
a/b/c  ; a PATH!

While decimal numbers like 10.20 have proven controversial to switch to be TUPLE! (e.g. by pushing DECIMAL! to use the 10,20 international format), they should be able to hold everything else PATH!s can today (which includes BLOCK! and GROUP!).

PATH!s have also become more interesting in terms of ways to augment a single WORD!...subsuming REFINEMENT! and also allowing forms that have a BLANK! in the second spot, rendering with a terminal slash:

/a   ; now a PATH! (with a BLANK! in the first spot)
a/   ; also a PATH! (with BLANK! in the second spot)
/a/  ; this works too (3-element PATH! with blanks in first and last spots)

Then of course, these would apply to TUPLE!s as well, giving you fun new tuple variations:

.a
a.
.a.

This gave me an interesting idea about how to define the behaviors, which could also solve longstanding concerns in the system:

What if TUPLE!s evaluated exactly as today's GET-PATH!s would, never executing functions?

This would let you bulletproof your code a bit better when picking things out of objects that you expect to be values. You'd use dots. Terminal dots could be useful too...as a slightly more subtle way to ask a WORD! not to execute than to use a GET-WORD!.

append block data.

You could read that out as "append block, data...PERIOD." Do not call data, it is not a function. This would mean :my-object/field/subfield could look much cleaner as my-object.field.subfield (no terminal dot needed since the last internal split is a dot).

I'd suggest then that ending with a slash means that you guarantee the thing you are calling is a function.

append block data/

If you don't want to specify you leave it off, as today.

My proposal would be that only terminal slashes act this way...for compatibility. It would be your choice to use a slash internally to a path for polymorphism (as there is no other polymorphic option).

With this, you could then write:

obj.member-obj.method/refinement whatever

If you were in the practice of only using / when calling functions then this would unambiguously suggest to you that method must be a function. Again--this would not be enforced.

I think this would really help decipher expressions like a/b/c/d/e/f if they were written as a.b.c/d/e/f or a.b/c/d/e/f or a.b.c.d.e/f (or a.b.c.d.e.f/ or a.b.c.d.e.f. if you wanted to be more explicit about whether the end of the chain results in a function call or value).

There are some bigger implications here about having another way of indicating suppression or execution with the terminals. I've often worried about the ambiguity of whether GET-WORD! meant suppressing execution or tolerating undefined values...which we had made an interim resolution to say that it only suppressed execution, and GET/ANY was necessary to get voids (as in Rebol2). But this throws in new possibilities so that should probably be rethought.

1 Like

Note that although this proposal would take away "." from WORD! characters, it would not remove "." from being able to reach values. So if you had charsets.latin1 it could still find it, but you'd just have to have CHARSETS defined as an object.

The twist would be that if you wanted to call a function with . in the name, you'd have to stick on a terminal slash (or use REEVALUATE (abbreviated REEVAL), or APPLY, etc).

some.fun/ arg1 arg2
reeval some.fun arg1 arg2

But I do think it builds on solid logic to the idea that period isn't a word character (while it makes sense for apostrophe to be). If you use periods it really seems like you're indicating some kind of structural intent...as opposed to hyphens or underscores, that are more typically "parts of the word".

(Note: I'm very tempted to think that this could all be used somehow to suppress assignment of functions, e.g. foo: .bar or foo: bar. will guarantee bar is not a function, or something like that. But it needs to be thought through consistently. This gets at some of the bulletproofing that I think is worth it for libraries, but maybe not the average user code... )

1 Like

:clap: I think this is clever, I'm curious to see what kind of reception this gets.

1 Like

So the rule for the TUPLE! would be "things to the left of a dot can't be functions".

append block some.item   ; SOME can't be a function, but ITEM can...not executed
append block some.item.  ; neither SOME nor ITEM can be a function

I'm not sure what the implication would be for a.b/c.d when b is a function. :-/ This would mean C and D would be refinements or something? There's no need for it to have meaning...it could just be an error (and saved for dialects that wanted to make use of it).

What's really neat is that the trick for using single cells for PATH!s that contain only one item has now been deployed for the / trick that @giuliolunati wanted. It's creaky at the moment, but the concept is proven. That means we can get all of [.a a. .a. /a a/ /a/] each for the same price as a single WORD!.

2 Likes

This means decimal numbers in paths/tuples will require forming with comma ",". It is somewhat similar to the date case, in that date slash-forms cannot appear in paths/tuples, however in the decimal number case the form with comma is not the default form. So, decimal numbers will form differently whether they are in a path/tuple or not (unless you want to change the default form to use comma, which I am against).

In other "random thoughts", I think that PAIR! and TUPLE! can be unified...such that a 2 element numeric-oriented tuple renders with an x. Then PAIR! would be a "type constraint class" (like how REFINEMENT! will be a constraint on PATH!s of length 2 with blank at the head).

This would save on a lot of redundant elementwise code...recover one of the 64 internal type bytes and bring PAIR's 2-element-"pairing" series optimization) where 2 REBVAL fit in a REBSER node with no external allocation) to PATH! and TUPLE!.

Type constraints are effectively predicates, so maybe they would be done e.g. as .refinement?

 >> any .refinement? [/a/ b/c /d ///e]
 == /d

 >> print-pair: func [pair [.pair?]] [print mold pair]

 >> print-pair 'a/b
 ** Error: print-pair expects its pair argument to match PAIR? constraint

I dunno. Also, there's a question of whether 1/2 should be legal as a PATH!. It currently conflicts with DATE!, but should probably stick to 12-Dec-2012 forms...because slashed numbers seem more valuable to have...and it makes up for not having 1x2 instead of 1.2

I was reviewing an old post in which I Reacted to Red's Design Issues Wiki, where the question was asked:

"Why do paths evaluate picked items (so-called active accessors)"

Boris says the below in Gitter chat (emphasis mine)

This is actually harmful. By writing block/1 you are meaning to do what? To call the first function of a block? What a nonsense. Most likely you are meaning to fetch first item. That's just how it reads.

When your path is not numeric, but something/word then on the other hand it's likely a function call or an attempt to get the value of the associated word. It reads differently, and in this case evaluation is what you would expect. Yeah I know about get-path etc etc. But I also know from my little experience this is a source of bugs, and affects not just newbies (we're all human after all, and everyone is lazy to spare the extra colon).

Just To Cover All The Bases, I'll Mention An Idea

Today we can put a slash after a function call to enforce that it is a function, or a dot after a non-function:

>> append [a b c] negate.
** Error: NEGATE is ACTION! so you can't access it with . as NEGATE.

>> orange/
** Error: ORANGE is not an ACTION! so you can't invoke it with / as ORANGE/

And this applies to path components. So each time there is a dot it enforces the the thing to its left is not a function.

This doesn't help us with the issue being mentioned, where something like data.1 will run a function even though you probably didn't want it to (but might have).

Which leads to an idea I'd rejected but might deserve an articulation of exactly why:

What if the slash was used before a function to indicate it was a call?

obj/run 1 2 3   ; RUN is a function

obj.var  ; var is not a function

Examples

>> "Hello"
== "Hello"

>> system.console/last-result
** Error: system.console/last-result is not a function, use .last-result

>> system.console.last-result
== "Hello"

>> system.console.print-result 10
** Error: system.console.print-result is a function, use /print-result

>> system.console/print-result 10
== 10

Well, it solves the problem. But I can't help but read the slash before the function call as making it a refinement of the thing before it. :frowning:

Of course, one must bear in mind that historically that was system/console/last-result so it was even worse!

You can look at it as two different rules:

  • If you see a slash in something that's a mixture of slashed and dotted parts, you're looking at a function call, and the function is the thing after the first slash.

  • If you see a slash in something that's only slashed components, you're also looking at a function call, and the function is the thing before the first slash.

Or you can try to look at it as just one:

  • Slashes put you in the mode of making a function call if you weren't already in that mode.

Could We Learn To Like It?

I never even wrote this up before because it rubbed me the wrong way.

But when I take my kneejerk response out of the equation, I realize that the benefits of being able to identify a function call are very high.

Okay, the thing I want is in the system object...

system

And it's another object, the console...

system.console

And I want to call a function...

system.console/

It's called PRINT-RESULT...

system.console/print-result

That seems like a pretty reasonable set of steps, but plain refinements don't call functions:

/append

Rather, that's inert and terminal slashes call functions:

append/

Other Arguments Against: Structural Separation

When you write obj1.obj2.method/refine1/refine2 that is a PATH! of 3 elements. The first element of that path is a TUPLE! with 3 elements.

There's something architecturally nice in having all the selection logic happen in the first tuple. We can truly split the responsibility of what a slash does and what a dot does. Slashes do refinements, dots do selection.

But mixing it up to where slashes are involved in selection feels like it's going backwards against aesthetic and design advancements that have been made. I dunno.

My Instinct Still Says The Cure Is Worse Than The Disease

I know it's a great feature to know when things are a function call or not. It's very alluring to be able to be getting it in these cases "for free" in terms of character count.

But... I... just don't like it! :face_vomiting: