Big Alien Proposal 🛸 "/WORD" Runs Functions

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.

Well, I like it--and I have some number of years of working in the medium under my belt. So let's see how you feel after some time using it. :slight_smile:

(It's coming along reasonably but it's quite the change.)

For that matter, let me see how much I like Ren-C in general after using it! It’s fair to say that I’m commenting from the sidelines here.

(Haskell-Brassica is shaping up nicely, so hopefully the ‘using it’ will come sometime soon…)

1 Like

In practice, I've found that it's really just too easy to type something like lib.append and expect it to run, because you casually forgot to write lib/append ... and it's not as easy to debug as one might think.

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

I therefore think that if lib.append is to fetch an antiform action, you have to write lib.append. with a terminal dot.

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

If that's not your style, you could instead write get $lib/append (or get $lib.append/) -- which has the advantage of ensuring the thing being gotten is an action.

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

If it can be an action or something else (e.g. maybe it is null), use get $/lib.append

The GET itself may have a refinement to allow active GETs.

/old-append: get/active $lib.append
old-orange: lib.orange

That would probably be the tolerant version (e.g. would allow lib.append to be null, etc.) Rather than add another refinement for "I'm getting an action" that could probably be better done as GET-ACTION.

I’d be OK with making lib.append fetch the action. That way, it’s consistent with an ordinary function call append, and you don’t need the lib/append syntax to conflate function calls with tuple access (which I never liked).

I’m not so happy about the terminal dot, though… it seems like something which would be really easy to miss when reading code. It seems reasonable to make people use get $lib.append/ for that comparatively rare case.

So I've decided that terminal slash has the best application as fetching an antiform action without running it.

This is a very commonly needed thing, and representing it with a slash at the end offers the dual benefit of saying "this is something dealing with function dispatch" (as slashes now are exclusively), but also to throw up a kind of barrier between the function and any potential arguments...to help reinforce that it doesn't take them.

for-next: specialize for-skip/ [skip: 1]
;                         ---^
; slash helps reinforce the block is not argument

Much nicer than using get $for-skip, and you even have a typecheck built into it, like:

for-next: specialize (ensure [action?] get $for-skip) [skip: 1]

I've gone through and applied this and it's very good. A lot nicer than GET-WORD! ever was, and avoids the nasty conflation of tolerating unset variables as well as defusing actions...which I have always despised.

red>> type? :append
== action!

red>> type? :asdfasdf
== unset!

Terminal slash has one job: give you back an antiform action as-is...and if it isn't one, error.

So all right, leading slashes should probably mean function call always, when not used in an assignment.

But assignments are tricky. I feel this much is firmed up:

 value: ...      ; can't assign an action
/value: ...      ; might assign an action
value/: ...      ; must assign an action

It would seem that /value/: is inherently contradictory. Happy to rule it out for now.

Think we know this for a fact:

obj.field: ...   ; can't assign an action

I think this would suggest for the pattern that prefix slash used with tuple might assign an action:

/obj.field: ...  ; might assign an action

And for consistency, I think it is best to say terminal slash on a tuple acts as it would on a word:

 obj.field/: ...  ; must assign an action

Then we can rule out /obj.field/: as meaningless.

Then we have the 4 internal slash cases for obj+field. For the sake of brevity, I think obj/field should be a synonym for obj.field/... one less character

obj/field: ...  ; must assign an action

As for the stragglers... hm. I dunno.

 /obj/field: ...  ; ???
 obj/field/: ...  ; ???
/obj/field/: ...  ; ???

Conservatively rule them all out for now, I suppose.

One Gap: Run Sequence If Function, As-Is-If-Not

So WORD! has the fuzzy ability to run it things if they are functions, or give the value as is if not (or error if it's a tripwire or nothing).

But if your value is in a context and needs to be selected out of it, the current model forces you to pick either path (run it) or tuple (get as is).

I don't want to relax the tuple rule. It's way too easy to mess up and say obj.method and have it fetch inertly and throw everything off. At minimum you should have to say obj.method. to get that. And I don't want plain obj.method to be willing to invoke actions, because that loses the whole advantage of this strategy.

It's probably not wise to have this conflation except for arity-0 functions (how would you meaningfully handle something that sometimes consumed arguments and sometimes didn't?) And if it's an arity-0 function where you want it to act like a value, maybe that's a job for accessor.

It's something to keep an eye on, but I guess I'd like to see the motivating examples before trying to solve it.

1 Like

Seems to me that this could be made consistent with the SET-WORD! case:

obj.field     ; always give value as is
/obj.field    ; may be an action to run, otherwise give value
obj.field/    ; must be an action to run
obj/field     ; same as obj.field/

In practice, it's way too easy to mix up obj.field and obj/field. And it's a very frustrating bug if you thought you were running a function but it just evaluates to the function and keeps going. So I think obj.field being an error on an action is important

It doesn't get any less likely to make the mistake when you throw in a leading or terminal slash. So I think that /xxx for any XXX not running a function causes more harm than good. And similarly, knowing that xxx/ will give you an action as-is has too much value as a guarantee.

I'm finding that I really like the terminal slash to mean both "it's a function" and "don't run it".

apd2: specialize append/ [
    dup: 2
]

In the new model, slashes are tied to function mechanics. And having it in that position helps throw up a barrier that says "nope, the next thing is not an argument.

And I like being able to specialize right from a chain:

>> apd: append:dup/

>> apd [a b c] [d e] 2]
== [a b c [d e] [d e]]

...BUT...

In practice, the "I don't know if it's an action or not" does come up pretty often, and I think having a different syntax would be nice.

I Think We Should "Use The Parts" :building_construction:

For instance, there was always a question of if GET-WORD! should allow you to get unset variables... I mentioned above how I dislike this conflation:

red>> type? :append  ; you might have used GET-WORD! to get as-is action
== action!

red>> type? :asdfasdf  ; but you would get as-is UNSET!s too :-(
== unset!

So at least there, we could do better with:

abc/         ; get what's definitely an action antiform as-is
abc/~        ; get as-is if action, but nothings/tripwires/holes ok
/abc: ...    ; assign what is definitely an action
~/abc: ...   ; assign what could be an action or vacancy
abc/~: ...   ; maybe same, but doesn't look as good imo

That's rather communicative, and avoids the frustrating conflation. You can get some nice behaviors there, e.g. the following need has come up in practice several times:

~/foo: other/~  ; copies what may be antiform action or unset OTHER

And it's not throwing out anything, e.g. if you can write negate/multiply/ and that's some sort of function composition, you aren't at risk of multiply/~ taking away the ability to compose with a function called ~ because that's a blank quasiform and it can't be assigned to name a function.

So the question turning around in my head here is: is there some syntax jumble of this form which could meaningfully communicate "action-or-plain-value" for purposes of setting and getting.

At first I thought maybe terminal dot on a tuple would do this. But it's very subtle. Also, the meaning for leading dot as select member out of "current object" is going ahead, and that would put a lot of asymmetry on the trailing dot meaning.

If this would support anything, it would be terminal dot plus ~ for "vacancy okay on non-action".

abc.def      ; get non-action as-is
abc.def.~    ; get non-action as-is, vacancy okay

But here we have the issue that you can legitimately pick ~ out of things, so you're losing a capability.

All this makes it feel more like it's better to diminish the constraint slash than try to augment the ability of dot.

There's #...it can't be reassigned

abc/         ; get what's definitely an action antiform as-is
abc/#        ; get as-is if action, but other values ok
/abc: ...    ; assign what is definitely an action
#/abc: ...   ; assign what could be an action or other value

Oh, oops. / is valid in "ISSUECHAR!" literals at this time, so #/abc: doesn't put # at the head of a path, it makes an issue with a slash in it.

But that is changeable. You have to put some characters in quotes, e.g. #), is not a parentheses character... you have to do #")" otherwise (#) couldn't be a space character literal in a GROUP!. Slash may just be added to the exceptions, so #"/" in order to allow # in paths. This is certainly a motivating case.

I definitely think there is value added by distinguishing this, and letting plain slash in the most common case convey "I know this is an action".

This pattern without the ~ comes up a lot, and looks like:

/foo: other/

What makes it weird is that it feels like the slashes are a paired delimiter, like [foo: other]

It does take some getting used to (moreso I think than /foo: func [...] [...] does)

The "paired" feeling would be lessened with:

foo/: other/

But I don't like that. Putting the slashes next to the colon is bad for a number of reasons (e.g. you can't pick out a part of that which is just foo:).

I think the answer is that you use GET if you are bothered by the look.

/foo: get $other

(although I've been mulling whether GET should be willing to give you actions unless you use a refinement...but...it probably should be willing to. Still looking at how all this works in practice)