PATH! usage for Function Dispatch Only Unless in Redbol Mode

For a while now I've been a pretty big convert on the look of dots for member selection. It's not just that it's more standard--I think it's visually better. It also lets you be reassured that when you see a slash in a path, that the thing to the left of that slash was a function:

foo.bar.baz
   ;
   ; ^-- I may not know what foo.bar.baz is, but at least I know that
   ; foo and foo.bar are *not* functions

foo.bar.baz/mumble
   ;
   ; ^-- Under the convention that slashes are for refinements only, I
   ; can tell the author intends that (foo.bar.baz) must be a function

foo.baz.bar/
   ;
   ; ^-- With Ren-C's path generality, you can even decorate a case
   ; without refinements to show it's a function.

It Hasn't Been A Rule, but...

So far Ren-C has been allowing you to use slashes wherever you historically could. So the new rules were only applying to dots--restricting them on not being usable on functions.

append.dup [a b c] [d] 2  ; this would cause an error, for instance

However, you could still pick members out of objects with slashes:

>> obj: make object! [field: 10]

>> obj/field   ; not obj.field
== 10

I had a feeling this might be prohibited eventually...though it would need to be possible to make Redbol emulation work. So there'd have to be some kind of flag for allowing it.

...But Now, I Might Have A Good Reason To Enforce It

The reason is that in trying to do a good job of building an extensibility mechanism for member selection, it is difficult to make that mechanism able to communicate information about specialized functions in a "light" way.

When pathing is done hardcoded in the evaluator, it can do little sneaky tricks to push the words of refinements onto a stack. It doesn't have to create an entirely new specialized function.

But once you're using a generic interface to usermode functions which can extend PICK* and POKE*, that interface has to speak in "reified" forms. We have partial specialization so these refied forms exist... I just feel like what's happening in that case isn't "picking" or "poking". And it's tying my hands to make anything efficient if we say that path dispatch runs through code which might be usermode.

The Flag Will Be Introduced Gradually

I've been experimenting with the flag turning itself on automatically, and giving you a warning. So you only hear about it the first time.

>> obj: make object! [x: 10]

>> obj/x
The PATH! obj/x doesn't evaluate to an ACTION! in the first slot.
SYSTEM.OPTIONS.REDBOL-PATHS is FALSE so this is not allowed by default.
For now, we'll enable it automatically...but it will slow down the system!
Please use TUPLE! instead, like obj.x
== 10

>> obj/x
== 10

What happens when you enable the flag is that it actually turns any PATH! with no ACTION! in the first slot into a TUPLE!, and then permits you to use refinements in TUPLE!s like append.dup. This is because I'm avoiding creating a separate extensibility mechanism for paths...it just does the not-easy-to-optimize extensibility.

I haven't committed this, and I'd be phasing it in slowly. But as it's phased in, the performance of paths for member selections vs. tuples will degrade. So it's worth knowing about.

2 Likes

Let me paint the statement of what PATH!s are "for" in a broader way...

PATH!s are not for lookup, but are for "OPTIONS"

There is a feature of FAIL which is that you can pass it a location to "blame". It's a skippable argument, which you currently pass as an @-word:

greet-negative-number: func [message [text!] n [integer!]] [
    if n >= 0 [
        fail @n "It's called GREET-NEGATIVE-NUMBER, bozo!"
    ]
    print ["Hello" n ":" message]
]

It's nice because instead of seeing the error location as being reported from inside of GREET-NEGATIVE-NUMBER, it points at the callsite... and tell you the name of the offending argument... and its offending value.

Plus, both the error "reason" and the thing to "blame" are optional. This means if you're lazy you can just blame the parameter without a message:

if n >= 0 [fail @n]

You still have the name of the variable, its value, and the better implication of the source of the error at the callsite. Though if you want to just be lazy you can just leave it all off and have a generic failure... which is especially nice in things like SWITCH:

switch x [
    1 [print "one"]
    2 [print "two"]
    fail
]

Back To The Point: About PATH!s vs. TUPLE!s

So certainly if you're going to blame a variable, it should be possible to blame not just WORD!s but things that are looked up in objects.

obj: make object! [n: 1020]
if obj.n >= 0 [
    fail @obj.n "Let's say I want this to be negative, too"
]

** Error: Let's say I want this to be negative, too
** Blame: obj.n is 1020

But then I started thinking about the idea that you might have something else to say. Like whether to show the name of the variable in the error.

fail @obj.n/anonymous "Let's say I want this to be negative, too"

** Error: Let's say I want this to be negative, too
** Blame: 1020

If "PATH!s Are For Options", could values have them?

Fancifully:

>> n: 304

>> n/lastdigit
== 4

This blurs the line between "member access" and "option". Perhaps if you can only read a property but not write it, then it's more like calling a function and getting a result?

I'm not exactly sure where this line of argumentation goes. I just thought the application of pathing to add options to the blame argument of FAIL was interesting enough to write up and add to this thread.

1 Like