Big Alien Proposal 🛸 "/WORD" Runs Functions

I always felt like there was an elegant symmetry to:

:word  => get
 word  => evaluate
 word: => set 

The evaluation of a primitive being itself and the evaluation of an action being it's result. Unless I'm forgetting fringe cases, wouldn't this in practice make word! and get-word! effectively the same at that point? I mean if this is where we are at then why not go full Leeroy Jenkins on it and force all value accesses through get-word! and restrict word!s only to evaluating actions. Of course I could just be brain farting at 2 in the morning.

It's worth asking, but... the entire purpose of the language is to sort of be able to "animate" declarative data so that it executes, usually with an English-like aesthetic.

:xxx isn't a particularly natural thing to see or type--we don't put colons before things in writing. It would make things much uglier.

More a gimmick... and the 90% similiarity between WORD! and GET-WORD! mean it's not that useful. The difference is mostly just there to bite people who are trying to write generic code when they forget.

There's a lot more elegance with simply WORD! for getting, and SET-WORD! for setting. And if there's some reason WORD! wasn't good enough for getting, then the value is probably "ornery" in some way...so when you fetch it, then ideally it should go through a transformation that makes it less ornery.

This is why there are ^META words...

>> x: 10

>> ^x
== '10   ; quoted 10

The added quote level defuses things at "quote level -1", which are called antiforms. These are the things with "odd" behaviors (like unset variables, definitional errors, run-via-word-reference FRAME!s). The ^META step means they are easier to work with and pipe around...because they have been reified as quasiforms (so they can be put in blocks and are otherwise normal). Then you "unmeta" them to return them to the form that's "too hot to get".

But this is for when code needs to be sophisticated...and my aim is to empower that sophisticated code. So if someone would say "I'm not that worried about things like picking a function out of a block, because I never put functions in blocks"... they need to think about what life is like for the people writing libraries. If good generic libraries aren't pleasant and fluid to write--with that property of correct by default that I mention... the end users won't have a very interesting ecology to write code in.

2 Likes

I can see the benefits of this proposal especially from a security point of view. If we are thinking of using RUN then I am not convinced that / sounds like a run symbol.
> is an extra key press, but it looks much more like a run action to me. We may have to consider a different prompt symbol.

>> >foo 10
X is 10
1 Like

A fair thought...I'd say if there's anything so far bothering me with the prototype is that refinements are slashy, and the conflation starts making me think that those should be executable actions.

But how would we be representing these things?

obj>accessor
obj>accessor/refinement
obj.subobj>accessor/refinement
obj.data

This involves coming up with ways to wedge that > (or whatever) into the existing reasonably-well-thought-out-and-working TUPLE! and PATH! mechanics. What structure would these things be building?

There might be a RUN-WORD! like >foo, and perhaps we could make an argument that RUN-WORD!s are allowed in PATH!s (at the head only, maybe?) and get something like:

obj.>accessor/refinement

But... the thing that the slash idea has going for it is just that it seemed fine to say that obj/accessor ran a method before. And a plain word could run an action as well. So when you tie that together with the idea of obj.data for picking a field, it makes the slash seem like an acceptable "method" indicator. A global slash is just a method on the global context or module itself.

Appreciate the thought, good to keep the ideas coming...

I managed to get a booting system where you have to use /foo: assignments to get functions to execute when dispatched by WORD!.

TL;DR - I Don't Think I Like The First Attempt... BUT...

It's valuable exploration, to look at the options and think about alternative designs.

I Explored Having FUNC Not Return FRAME! Antiforms

Having Action be "FRAME!s at quote level -1"... that cannot be put in blocks, and can represent a non-literal sort of usage of actions... is very new. Like a-couple-weeks-ago new.

And in the first cut at implementing them, consider what would need to be true for this to work as expected:

foo: func [x] [print [x]]

In this world, for the word FOO to run the function, that meant the return product of FUNC would have to be a function antiform.

That got ugly. The whole idea of antiforms is that most routines don't touch them, and error when they encounter them. But this meant every generator (FUNC, LAMBDA, ->, ADAPT, SPECIALIZE, CHAIN, ENCLOSE...) had to make antiforms. And everything that processed or composed functions in any way had to either take antiforms or force you to ^META (or "UNRUN") your generated functions.

I tried throwing in random decaying from antiforms to normal actions, but it felt uncomfortable.

That's because I imagined no one would want to write the "clean" answer:

foo: anti func [x] [print [x]]

And probably wouldn't be much better with a specialized native like RUNS that only antiformed actions (in the spirit of how SPREAD only antiforms lists):

foo: runs func [x] [print [x]]

I suggested even stealing something light like arrow:

foo: -> func [x] [print [x]]

But the search for minimal also dovetailed with the desire for lib/append to reasonably imply /append was a function. So that's part of what birthed the comparatively-palatable idea of slipping it into the assignment with a single easy-to-type character:

/foo: func [x] [print [x]]

So it's good to try that out. Not having weird decay operations everywhere feels more stable.

But...

Incongruity With Refinements Feels Worse Than I Thought

We've already discussed the overlap with APPLY, where leading slashes don't mean function execution, like:

 apply :append [series value /dup 3]

But here, take a look at trying to move the CASE refinement name out of the way so that CASE the function can be used inside a function:

/alter: func [
    return: [logic!]
    series [any-series! port! bitset!] {(modified)}
    value
    /case "Case-sensitive comparison"
][
    case_ALTER: case
    /case: :lib.case
    ...
]

I'm not bothered by the number of slashes. I'm bothered by them meaning such unrelated things, yet being used so close to each other in such a core construct.

Other Than That, Slash Kinda Helped Code Readability

If you ignore the refinement debacle, there are some perks. If we tie together slashiness with "runs a function", then I've already pointed out how this feels pretty natural:

obj: make object! [
    data: 1
    /accessor: does [return data + 1]
]

But additionally, there are lots of "function generators" out there, with even more in the future. If you see something that says:

something: make-processor alpha beta gamma

Knowing if that SOMETHING is an action or not is a fairly important thing. It means you have to think about how you handle it carefully, even if just passing it around to somewhere else. So if you see:

/something: make-processor alpha beta gamma

You have to know the function-or-not at the callsite. Given that you have to know it when you reference it, then it seems to make some sense to declare it.

But I feel uneasy about treading on refinement's turf. And among things in Rebol that seem actually kind of decent, the refinements in function specs actually do feel kind of "good".

Forgetting The Slash Was Certainly Annoying

We don't have any new-users who are only habituated to a world where you have to put in the slash. So there's just going to be people who are used to not needing it, forgetting it.

This does bring about a shift from the "over-execution" of functions when you forget a GET-WORD!, to an "under-execution" of functions when you forget a slash.

Any Synthesis Of Ideas Here That's Not Terrible?

It should be remembered that antiform-based Actions are new, and their potential benefits are not completely understood yet.

We've seen great benefit from the antiform blocks, resolving the /ONLY debacle. And there are ideas behind antiform actions and typesets and such, for solving similar problems of wanting non-literal-value semantics.

The part I liked about this prototype was making things like FUNC and SPECIALIZE and ADAPT and everything else just return regular FRAME!. It felt cleaner to shift this burden of things becoming antiforms onto the caller, because otherwise the burden was on decaying antiform actions to regular actions. That burden showed up when fulfilling action parameters, or inside COMPOSE or REDUCE, etc.

But looking at the two strategies side-by-side, I have to say that it's probably better to lean into the "generator functions" producing antiforms, and work from there.

3 Likes

After about two years of learning to work with antiform-based actions, they are reasonably understood (and understood as the antiform of FRAME!)

The idea of FUNC returning plain FRAME! and then having a refinement-style assignment promote (demote?) it to an antiform is bad.

>> foo: func [msg] [print ["This" name]]
== #[frame! [msg]]

>> foo
== #[frame! [msg]]

>> run foo "Sucks"
This Sucks

>> /foo: func [msg] [print ["This" msg]]
== ~#[frame! [msg]]~  ; anti

>> foo "Sucks As Well"
This Sucks As Well

It is way too easy to forget the slash.

But I've Been Up All Night Smoking Crack. So Hear Me Out...

What if FUNC returned an antiform FRAME! and you had to use the slash in order to say "yes, I meant to assign that?

>> func [msg] [print ["This" msg]]
== ~#[frame! [msg]]~  ; anti

>> foo: func [msg] [print ["This" msg]]
** Error: Cannot save antiform frame with foo: (use /foo:)

>> /foo: func [msg] [print ["This" msg]]
== ~#[frame! [msg]]~  ; anti

>> foo "Might Have Potential"
This Might Have Potential

/FOO: and /FOO could mean "antiform frames are ok to be assigned here.

  • You can use it in function specs, e.g. func [a] [...] would not take an antiform action, but func [/a] [...] would.

  • You could use it in enumerations, e.g. for-each [key /value] obj [...] would allow you to receive frame antiforms, but for-each [key value] obj [...] would not.

Crazy Talk :crazy_face: What If Refinements Were CHAIN! ?

Under the rule above, you'd have to make your methods in your objects look like this

obj: make object! [
    data: 1
    /accessor: does [return data + 1]
]

Let's say that in order to call ACCESSOR you had to use a PATH!, e.g. OBJ/ACCESSOR. And then...for refinements...we use CHAIN! ... our new interstitial peer to TUPLE! and PATH! that uses colons.

This would make :word the new refinement (blank-headed chain):

obj: make object! [
    data: 1
    /accessor: func [arg [integer!] :refine1 [integer!] :refine2] [
        if refine1 [
            return data + arg + either refine2 [negate refine1] [refine1]
        ]
        return either refine2 [data + negate arg] [data + arg]
    ]
]

And if you want to use those refinements:

obj/accessor:refine1:refine2 10 20
  • By looking at it, you can tell that's a call to a function... it's only the weird exception of WORD! that were assigned with /word: where you look at word and can't tell in advance

    • compare with Redbol foo/baz/bar/mumble - You have no idea what's going on.

    • Ren-C made an improvement with foo.baz/bar/mumble where you know due to the use of refinements that foo.baz must be a function, but you're still out of luck if you just see foo.baz

    • This gives us foo/baz:bar:mumble, where we get a more intuitive sense of where the function is (it's /baz, the thing after the slash) and in the diminished case we'd still know foo/baz was a function call

  • Field selection via dot of actions could always not run the action even if it's an antiform, e.g. obj.accessor just gives you the frame antiform as is

    • The impact of these casually extracted antiforms would be limited to places that were rigged to accept the antiform frames

(append:dup [a b c] [d e] 2) is different, but is it bad?

It shifts :foo to be unrelated to "getting"...it would be inert, and used in things like APPLY.

And with slashes being so applicable to function application, it seems inevitable that // be the operator (though you could use :: if you want, these will be WORD!s now)

append // [[a b c] [d e] :dup 2]

:dup would become REFINEMENT?. This would mean that leading colon is how you imply optionality, not slash.

>> [a b c]: pack [1 + 2 10 + 20]
** Error: not enough values in antiform pack for assignment

>> [a b :c]: pack [1 + 2 10 + 20]
== 3

>> reduce [a b]
== [3 30]

In these places, if you used a slash you'd be saying you'd accept action antiforms. You can use both a slash and optionality: /:c. And you can circle it too, @/:c, if you really want to !

Execution Suppression

Accomplishing execution suppression could be done with a terminal dot on a WORD!, or the usage of any TUPLE!. I always thought foo. as "I mean FOO. Period." had potential.

>> append [a b c] [d e]
== [a b c] [d e]

>> append.
== ~#[frame! [series value :dup :part :line]]~  ; anti

But now we'd have it so foo.bar would be enough to get the execution suppression without needing to say foo.bar. because even antiform frames won't run unless you say foo/bar :bangbang:

And there'd be far fewer occasions where you'd need to use foo. "just to be safe". Because you'd have a sense from wherever it was assigned if you didn't have to worry about it.

You wind up with a different look:

>> apply :append [[a b c] [d e] /dup 2]

vs.

>> apply append. [[a b c] [d e] :dup 2]

I can see how the period might suggest (apply append) [[a b c] [d e] :dup 2] if you didn't know what it meant. But if you don't know what things mean, then you can interpret things all kinds of ways.

It doesn't seem all that measurably worse--if it's worse at all. Looking past this one particular case, the benefits are enormous. And you can always say apply get $append [[a b c] [d e] :dup 2] if you want to be verbose about it. (This particular case of course has append // [...])

And We Preserve the /FOO to Run A Function or Frame

>> tester: make frame! append.
== #[frame! [series value :dup :part :line]]

>> tester.series: [a b c]
>> tester.dup: 2

>> /tester [d e]
== [a b c [d e] [d e]]

If it's already an action antiform, then the slash can just be commentary for the reader to say "if not obvious from the context, this is a function call"

:exploding_head:


I only just came up with this, but it's based on understanding the pros and cons of prior attempts, and the benefits are easy to articulate.

I am especially fond of being able to look at XXX/YYY and know that's going to run a function, and that XXX.YYY never will.

This critically narrows the scope of negative impacts of antiform FRAME! by making it only relevant to dispatch from WORD!

...and of course the security of consciously declaring when you're assigning something to a variable that can run arbitrary code from a mere word reference is very important.


By being forced to say foo.bar or foo/bar you can't have a situation of "run it if it's a zero arity function or just give me the value". But maybe that's what foo/bar/ could mean if it ever comes up.

2 Likes

When you look at the case of /foo: it makes sense that the slash is "above" the foo:

/(foo:)  ; this interpretation is correct

If it were the other way around, it would be like you were trying to set a function execution, which makes no sense.

(/foo):  ; this is not correct

The wrapping path is relaxing a constraint on what would otherwise be a stricter assignment (no antiform frames), and the assignment stays stuck together so you can pick it out of the path.

And /:a is the representation of a refinement that is allowed to be an antiform action!!

Since . is a WORD! now we know that /.: has a meaning... assign an antiform action to the dot operator. /: means run the FRAME! or antiform-frame resident in the : variable.

You'll need to use escaping to assign forms like ::: or // because you can't get the colon "on top" of them.

>> ($:::): 100

>> :::
== 100

>> /($//): does [print "A small price to pay"]
== ~#[frame! []]~  ; anti

>> //
A small price to pay

It's all clicking.

:star2:
:flying_saucer:
:alien:

1 Like

Looking at some practical examples of the consequences, of course every time you want to assign a function you now have to say /foo: func [...] [...] instead of just foo: func [...] [...]. While that may seem a little irritating when it's FUNC, it's actually helpful when you're using something else... like someone reading /foo: cascade [...] for the first time will know that CASCADE is a function generator. So it's more helpful than it seems, and it's an easy-to-hit key on the keyboard.

it definitely pushes refinements down in terms of how much they stand out:

for-each [abbrev expansion] data [
    replace:all sig unspaced [space abbrev space] unspaced [space expansion]
    replace:all sig unspaced [space abbrev newline] unspaced [space expansion]
]

response: copy:part lowercase ask ["Okay?" text!] 1
if response = "y" [break]

surname: across to ","
thru space [opt some space]
[firstnames: across to "("] (trim:head:tail firstnames)
title: between "(" ")"

Colon just doesn't "pop" as much as slash does.

But I think this is the correct tradeoff, because it means slash now pops as showing you where functions are being called or defined, which is more significant.

We're getting more bang for our character buck.
:dollar:

Emulation of historical Redbol gets tricky. But new techniques could address it, when and if it becomes a priority for...someone.

BUT WAIT, THERE'S MORE: AN INSANEO-STYLE BENEFIT!

  • path means execute function

  • chain in path means pick refinement

  • tuple in chain means provide argument there !!!

This gives us what everyone always wanted.

>> lib/append:dup [a b c] [d e] 2
== [a b c [d e] [d e]]

>> lib/append:dup.2 [a b c] [d e]
== [a b c [d e] [d e]]

>> lib/append:dup.2:line [a b c] [d e]
== [
    a b c [d e]
    [d e]
]

And it means we can do away with skippable parameter hacks (in fact, we can do away with skippable parameters altogether):

>> compose:only.<*> [(1 + 2) (<*> 10 + 20) (100 + 200)]
== [(1 + 2) (30) (100 + 200)]

(That's an actual legitimate use of an :ONLY refinement...)

It would be literal, so you'd have to use a GROUP! to evaluate. So if (a: '**)

>> a: <*>

>> compose:only.(a) [(1 + 2) (** 10 + 20) (100 + 200)]
== [(1 + 2) (30) (100 + 200)]

Stratospheric: CHAIN! as The Function Call Dialect

What if you offered the ability to pass the chain to the function so it could handle it itself?

>> all:not [1 > 2, 3 > 4, 5 > 6]
== ~true~  ; anti

Instead of being constrained to a finite number of refinements, you could interpret it how you wanted.

Maybe a version numbering scheme to make the same function have different behaviors, without needing more than one top level declaration... something:1 vs. something:2 as a shorthand for something:version.1 and something:version.2 ?

Hence compose:<*> could be the presumed way to give the limit of what you compose. You could even change the interpretation of parentheses or fences or blocks...

>> compose:[$] [a b c [1 + 2] [$ 10 + 20] (100 + 200)]
== [a b c [1 + 2] 30 (100 + 200)]

>> compose:(a) [a b c [1 + 2] (a 10 + 20) (100 + 200)]
== [a b c [1 + 2] 30 (100 + 200)]

Sky is no longer the limit.

This Is Looking Like A Done Deal

Not only is this going to give greater readability, greater security/sanity, a more coherent model for things I've bumped up against... but it's features I now don't think I can live without.

I will be beginning the change shortly.

2 Likes

This is the first time I’ve seen this thread, but I really like it! It feels like a great improvement on the basic Rebol notion of refinements.

(And, for once, I completely agree with the syntax. It reminds me of Potion, which I’ve always liked.)

2 Likes

I like it. It’s really not hard to understand. Might take a little getting used to, but I think the benefits are clear.

I’m on board the spaceship.

1 Like

2 posts were split to a new topic: Function Composition via PATH!

Responses To Potential Criticisms


append:dup.2:line is less readable than append/dup.2/line because : visually does not separate the words so strongly as /.

Yes, I will admit it doesn't stand out as strongly. But there are priorities.

To me, seeing XXX.YYY and knowing that will not run a function vs. XXX/YYY and knowing it will run a function (and that YYY is the function) is a higher priority.

Along similar lines, when I introduced generic TUPLE! I had some doubts about pushing things down and losing some of the "heft" of slash:

foo/some-thing

foo.some-thing

I thought "the dash now looks stronger than the dot", to where you might try to grok that as:

(foo.some)-thing

But the mind is fluid. You read things how you are used to them. While I found this a little upsetting at first, that upset quickly gave way as I re-learned. I don't even think it took a week. I now think nothing of it, and read it naturally. (For that matter, we are used to this in filenames, such as my-file.txt not being my-(file.txt))

My bet is that this will just be another such thing, to where the benefits will vastly outweigh the downsides.


Rebol's ambiguity problems are not solved with optional slash. Only with a mandatory one--which would be a lot of syntax, and reduce the flexibility.

Only WORD! are left as the fuzzy case. And I believe that it will be annotated enough at the boundaries that you will know what you are getting into.

foo: func [block /baz <local> bar mumble] [
    baz 1 2 3
    /bar: frotz 5 6 7
    bar 8 9 10
    mumble: splat 11 12 13
    append block mumble
    print "You know this print isn't an argument to mumble"
]

It's unavoidable that the "keywords" in Rebol have this character, and you won't want the slashes on them. But I am thinking this is "as good as it gets" in terms of letting you mitigate the burden of the ambiguity, and write clear and safe code.

Note that we have even more safety offered now with APPLY, which you might use whenever you're getting functions you aren't completely confident in the arity of:

foo: func [block /baz <local> bar mumble] [
    baz // [1 2 3]
    /bar: frotz 5 6 7
    bar // [8 9 10]
    mumble: splat 11 12 13
    append block mumble
    print "You know this print isn't an argument to mumble"

    ; ... and you know mumble isn't an argument to block!
]

You're losing readability and making the language even more filled with syntax of all kinds.

Consider the difference between these two things.

Traditional Redbol:

old-append: :lib/append
old-orange: lib/orange

vs. /WORD is active proposal:

 /old-append: lib.append
 old-orange: lib.orange

These are the same number of characters. But the second not only looks better, it has many nice properties:

  • It keeps you from having to use an ugly prefix colon when you're fetching APPEND as an active function out of LIB by value... using a tuple (dot) as APPEND.LIB is enough to ensure you won't run a function

  • The prefix slash on /OLD-APPEND shows your acknowledgment that what you're getting can be active (if you just said OLD-APPEND: it would be an error)

  • You are shielded from executing a parameter-consuming function from LIB called orange by using the tuple of LIB.ORANGE

  • If orange in lib happens to be an antiform active function, it shields you from assigning that to old-orange because you didn't say /OLD-ORANGE:

I'm really skeptical of the good faith of someone who has used Rebol/Red at all and won't acknowledge the superiority of the second form.

1 Like

I have a little bit of a qualm here, if /foo is a notation that in some places means "thing that a function can be assigned" and in other places means "invocation of a function".

It's only a small qualm. e.g. in for-each [key /value] we know that KEY isn't getting evaluated. In function specs like func [a] we know A isn't getting evaluated.

We don't want to shuffle it so that foo/ is the notation for invocation, because that breaks the basic premise of obj/foo having the slash before the thing that runs.

So resolving this would have to put terminal slashes on all the receiving sites.

foo/: func [obj action/] [
   let arg1: 10
   for-each [key value/] obj [
       if not action? value. [print "Not an action!"]
   ]
   let arg2: 20
   obj/method arg1 arg2
   action arg1 obj.field
]

I don't like that. It's ugly, and I think the leading slashes are much more natural:

/foo: func [obj /action] [
   let arg1: 10
   for-each [key /value] obj [
       if not action? value. [print [key "is not an action!"]]
   ]
   let arg2: 20
   obj/method arg1 arg2
   action arg1 obj.field
]

I'm not convinced the first form really offers much benefit at all in practice, and it certainly doesn't offer enough to make things look that bad.

I have no problems with this — the situation is perfectly symmetrical. It’s no different to how Lisps let you write things like

(defun (f x y z) body)

to assign f, and then

(f x y z)

to use it. Either way, the notation (f x y z) means ‘function f with parameters x, y, z’.

1 Like

This covers "actions are okay" but not "an action was expected".

Earlier I suggested that maybe foo/: could be that. But that sucks...it doesn't make any sense--we're putting slashes before actions, not after them.

It may not be necessary to make a single-value assignment notation that enforces actions. You could just write /foo: runs (whatever) and then RUNS can be tolerant of antiform frames (as well as normal ones). It will be intolerant of everything else (like integers). It's shorter than /foo: ensure [action?] (whatever) and good enough.

But I do think we could say with things like get $/foo that it ensures foo is an action (or FRAME!, that it would come back with as a runnable action).

/old-append: lib.append  ; would work but not guarantee it was an action

/old-append: runs lib.append  ; ensures append is some kind of frame

/old-append: get $/lib.append  ; ensures append is some kind of frame

That feels like it gets good enough coverage.

Function parameters have typechecking, so if you want an action you say /arg [action?]... whereas just /arg could get you something that may-or-may-not-be-an-action. I can live with that.

This is an error:

obj.accessor: func [...] [...]

But I'm assuming these forms are both legal:

obj/accessor: func [...] [...]

/obj.accessor: func [...] [...]

The first one saves you a character, so most people would probably prefer it.

But this offers us an axis of distinction. We could say that obj/accessor demands that what you are assigning is a function, while /obj.accessor merely tolerates it.

This really does leave me wanting for a way of annotating that you're assigning a WORD! that you insist to be an antiform action.

???/old-append???: lib.append

You'd have to be able to express it by putting something either in the space before or the space after--because I insist on the slash preceding the function.

I think with the meaning of slashes after an action being a chain, we can make a reasonable argument for old-append/: ... for those who wish to use it. But you can't use two, e.g. /old-append/: ... you choose one or the other.

Absolutely.

Overall character counts going down, clarity going up, safety going up... what I'm seeing from how much I've gotten working so far is great. I think people will get used to it quickly.

There's a little pain with code written as lib.append [a b c] [d e] because it just evaluates the lib.append to nothing and keeps going. But, new code written people will be expecting to always see a slash in a sequence if any action is to run. Historical Rebol didn't have any such TUPLE! so it'll really just be a matter of removing error-causing slashes... only Ren-C code has the problem (and I can take care of it for the codebases we care about enough to run on CI. I just have a breakpoint whenever a tuple evaluates to an action and go clean it up. Once all the accidental ones are clean, I can start using them intentionally.)

I've been wondering about some kind of warning on values which get evaluated and just skipped like that... throwing away values is useful sometimes, but I feel like we could be of some help there more generally.

1 Like

So I might have been negging trailing slash, in order to manipulate it and make it feel more complimented when I decided it was actually good looking. Or maybe just mistaken.

Let's look at some real-world code from Rebmake

Here's what the original would have sort of looked like (code is inside a method):

for-each dep project/depends [
    run/parent dep project
]
run-target make entry-class [
    target: project/output
    depends: join project/depends spread objs
    commands: reduce [project/command]
]

So now let's bring it into minimum compliance with modern Ren-C so it runs

for-each dep project.depends [
    .run:parent dep project
]
.run-target make entry-class [
    target: project.output
    depends: join project.depends spread objs
    commands: reduce [project.command]
]

Even in this tiny sample we see things got significantly clearer. We now know which things are methods or refinements, which are inert PICKs.

That's what you have to do. Though if you don't think the dots are worth it for the information, you could import the object into the code with a WITH or USE kind of function:

use .
for-each dep project.depends [
    run:parent dep project
]
run-target make entry-class [
    target: project.output
    depends: join project.depends spread objs
    commands: reduce [project.command]
]

Depends on what you're working on. If you're maintaining someone else's code like in Rebmake, where it's spanning page after page, I think calling out the object members helps a lot.

But whether you feel the need to annotate the function calls is up to you. Let's say you decide you do want to--even on the refinement based call, for attention:

for-each dep project.depends [
    /.run:parent dep project
]
/.run-target make entry-class [
    target: project.output
    depends: join project.depends spread objs
    commands: reduce [project.command]
]

Oooh. Burn. The leading slash doesn't look so good when you're picking a method. But what about the trailing slash?

for-each dep project.depends [
    .run:parent/ dep project
]
.run-target/ make entry-class [
    target: project.output
    depends: join project.depends spread objs
    commands: reduce [project.command]
]

It actually doesn't look too bad. And while the slash is after the function name, it still could be relevant here, to say this is a cascade of one function. So we have our one slash rule preserved. I can see how it's legit.

And further, I've been mulling over needing a way to say "I wish to apply this, whether it's a function or just a plain old value." With a WORD! you could do that by just not decorating it, but what if something is located in a tuple?

It seems to me that leading slash is likely the best way to serve that purpose. Meaning:

>> obj: make object! [field: 10, accessor: does [print "called", field]]

>> /obj.field
== 10

>> obj/field
** Error: field is not an action

>> /obj.accessor
called
== 10

>> obj/accessor
called
== 10

I also noticed a strange wish from BrianH for APPLY to be able to take values:

Function for Secure Evaluation of Values Which Might Be Functions

>> secure-eval 5 [probe 1 probe 2]
== 5  ; passes through non-function value

>> secure-eval :add [probe 1 probe 2]
1
2
== 3  ; applies the function to the block using APPLY rules

Anyway, I think when all is said and done that leading slash likely should have tolerance of non-functions, while trailing slash or internal-slash-in-path would strictly require functions.

This puts non-antiform FRAME! in limbo, to where I'd have to say only the internal slash or trailing slash would run them...leading slash would give the frame as-is. (You also have RUN for plain FRAME! which is more literate.)

I don’t like this, especially combined with function composition. Consider:

obj/field       ; obj.field must be an action
/obj.field      ; obj.field not required to be an action
/obj.field/fn   ; obj.field must be an action‽

This feels horribly inconsistent to me. obj.field suddenly means different things depending on what it’s nested in.

I’ll say again that I very much like the idea of separating ‘object access’ from ‘action execution’ (and ‘function refinement’). It breaks that paradigm to say that obj/field does both at once. I can reluctantly accept it if it’s defined to be a syntactic shorthand for /obj.field, but not if it’s a separate thing.

(As for following slash, I don’t have a problem with it. It feels reasonable to say that /f and f/ are two different ways to write the same thing.)

I feel it's easily learnable. And when you're in it for the long haul--as you are with a programming language--learnable things that have payoffs are worth it. (As opposed to "compromising a program's power and beauty...merely to force it to make a mundanely comprehensible first-impression").

I'm pretty well-versed in the minefields of problems for which this can help, while keeping things concise. I've acclimated to it quickly.

Someone starting out who wants to prefix everything with slash is free to do so, and later learn the other techniques.

Oh, it’s certainly easy to learn, that was never my issue. I just don’t think the payoff is worth it. Indeed, I think that this change ‘compromises the program’s power and beauty’ — the beauty being that of representing separate operations with separate operators.