Idea for Naming Function Arguments Out of the Way

As part of Rebol's contention-for-short-names problem, there are many instances where refinements have names like /ALL where they conflict with common natives or lib functions.

When this happens, I tend to do something to name them out of the way and put the lib function back:

foo: function [
    bar [block!]
    /all "foo all the bars"
][
    all_FOO: all
    all: :lib/all
    ...
]

Clearly we need a better pattern. Something that occurred to me today was the idea of being able to name refinements in the spec via a longer path:

foo: function [
    bar [block!]
    /x/all "foo all the bars"
][
    ...
]

The concept would be that the interface to the user (e.g. HELP) would still just show /ALL. But you as the function author could clearly see that you were supposed to refer to the refinement as /X/ALL in the body of the function. And the binding of ALL would be left alone inside the function body to whatever it would typically be.

For performance, the name you pick in the path could be set up as an alias for the frame of the function itself. So if you probed the x above you would see it is a FRAME! with bar and all inside it. If you wanted to, you could call x something more meaningful like this, or if you aren't making recursive calls you could even name it foo

The same could apply to normal arguments, just with no leading slash:

foo: function [
    x/bar [block!]
    /x/all "foo all the bars"
][
    ...
]

So that would leave bar bound as-it-was in the body, vs. binding it to the bar argument.

1 Like

Quoting from https://forum.rebol.info/t/quoting-decisions-in-practice-null-refinements/1166:
"Refinement-style-paths are like a GET-WORD! that's null-tolerant...they evaluate."

So wouldn't you be able to use /all everywhere you meant to use the refinement-valued all?
This would break a LOT of code, but, on the other hand, refinements now evaluate ...

That wouldn't help with non-refinement parameters with names you didn't want to override...unless you decided to make every use of an argument null-tolerant.

Plus, I like not using the null tolerant form in places you've established the refinement was specified.

foo: function [/bar [integer!]] [
    bar: default [1 + 2]
    ... ; no need to use /bar here, you know it's set.
]

To me, one of the big benefits of the new distinction is letting you convey to the reader your state of confidence (or lack of confidence) of whether something is set. GET-WORD! didn't do that, because of its double meaning.

As I've said, I think this is kind of tying together to build sort of the best-case-scenario for the questions that plagued refinements and refinement arguments in the early establishment of null.
That was when tradeoffs were being looked at with how Rebol2 would make refinement arguments blank in a way that might lead to them being accidentally used improperly when the refinement was not specified. Trying to couple it so that a falsey refinement always forced the refinement arguments to null was not as good as this unification...it seems to tick all the boxes.

foo: func [bar /all/all-FOO] [
     ...
     all [... all-FOO ...] then [...]
 ]

One thing that kind of displeased me about it is that if you did this with a normal parameter, it would get a slash in it. But tuples might avoid that. Also, the necessity to draw attention to it is less...because you see it up in the parameter list, so there's less odds you'll overlook it. So the capitalization can probably be dropped:

foo: func [bar.bar-foo /all.all-foo] [
    all [... all-foo bar-foo ...] then [...]
]

It might not be the most beautiful thing in the world, but this is a real problem that is very frustrating when it comes up. And when you encourage people to play with words, they shouldn't be afraid to reuse them in refinements if they make sense.

Objections? Better ideas? :cricket: :cricket:

1 Like

While reading this, I had the thought, that tuples might be useful here, so I think this is in support of your idea.
I'm a bit surprised at how integral tuples seem to become to the features of whatever ren-c morphs into :wink: as tuples have been one of the lesser useful datatypes of Rebol (at least to me).

2 Likes

Maybe there could be two features that work together on this. One would be the ability to ask that the function's frame be passed in as a variable, and another would be to suppress binding of an argument in the body.

foo: function [
    bar [block!]
    ./all "foo all the bars"
    <frame> f
][
    ... use F.ALL to access the instance's all parameter ... 
]

It's currently a little bit obtuse to get the function frame, e.g.:

let f: binding of 'return

So having an easier way to get it would be nice. And hinting that you would use a field reference to access the argument with the dot struck me as an interesting dialect choice.

One way or another, I think this issue needs to get addressed.

1 Like