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.