Refinement Arguments at Head of Args List, Not Tail

In the corpus of code we have so far, it seems to me that when a refinement adds an argument to a function that it would be preferable if that argument would become the first parameter... not tacked onto the end.

Some cases might not be completely obvious one way or another:

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

>> append:dup [a b c] [d e] 2  ; old way
== [a b c [d e] [d e]] 

>> append:dup 2 [a b c] [d e]  ; new idea
== [a b c [d e] [d e]]

I think it's better if it's first, but it's not earth-shattering.

But in other cases it seems very much an improvement. Consider the positioning of the argument to FAIL:BLAME...

foo: func [arg thing] [
    if arg < 0 [
        fail:blame [
           "Here is some long error message:" @thing
           "Whatever..."
        ] $arg
    ]
]

foo: func [arg thing] [
    if arg < 0 [
        fail:blame $arg [
           "Here is some long error message:" @thing
           "Whatever..."
        ]
    ]
]

Or an argument to COMPOSE giving a pattern to use:

compose:pattern [
    some bunch of {{code that}} <spans>
    #multiple lines
    [and could go on for pages]
] ${{}}  ; afterthought...

compose:pattern ${{}} [  ; forethought
    some bunch of {{code that}} <spans>
    #multiple {{lines}}
    [and could go on for pages]
]

This goes along with some Haskell philosophy I cited in Parameter Order in Rebol:

"It's common practice in Haskell to order function parameters so that parameters which "configure" an operation come first, and the "main thing being operated on" comes last. This is often counter intuitive coming from other languages, since it tends to mean you end up passing the "least important" information first. It's especially jarring coming from OO where the "main" argument is usually the object on which the method is being invoked, occurring so early in in the call that it's out of the parameter list entirely!"

These refinements typically seem to be configuring, as if they are changing the function itself, and belong at the head.

e.g. above, the function you're conceptually applying is (compose:pattern ${{}})

History Didn't Do It This Way, With Some Reasons

Refinements are typically listed at the end of the function spec.

From an implementation standpoint, that's also where their "slots" are in the argument list.

This means that as you are walking the argument list and fulfilling arguments from the callsite, if refinements were used you would have to skip over the "normal" arguments in a first pass, and then come back and fill them later.

Historical Redbols only had to be worried about the order of usage of refinements... if you used them out of order from the declaration, a second pass would be needed. But using them in order would not require it.

This isn't a problem for Ren-C...it's designed for generic parameter reordering (refinements or otherwise) and it has an efficient way to beeline back to slots it skipped on a second pass.

So really the only issue is the mismatch between the visual order in the spec (which may be exposed mechanically by fixed orders of enumeration of FRAME! keys and values), compared with the gathering behavior. But the disconnect of that order has always been there, with foo/refine1/refine2 vs. foo/refine2/refine1 in Redbol... the callsite order may not match the frame order.

Is It Worth Changing?

The competing (complementary) idea of CHAIN! dialecting offers something that's likely even more compelling:

compose:pattern ${{}} [  ; better than today...
    some bunch of {{code that}} <spans>
    #multiple {{lines}}
    [and could go on for pages]
]

compose:${{}} [  ; ...but this surpasses even that
    some bunch of {{code that}} <spans>
    #multiple {{lines}}
    [and could go on for pages]
]

Though it's kind of up in the air if and when that's going to get attacked, and how well it will work (it may run afoul of problems in binding, etc.)

My instincts tell me that it's worth changing. In practice, refinements that take arguments are not super common... but when they do happen, being up front seems to make the most sense.

2 Likes

A thought… might it be possible to do something like this?

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

Basically, this would mean that refinements don’t add new arguments per se — instead such cases can be unified with CHAIN! dialecting.

(I feel certain that we’ve discussed something like this before, but I can’t seem to find it with a quick search.)

Using another CHAIN! element was intended to let you drop the refinement altogether, to accomplish briefer things like:

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

But I was thinking TUPLE! could do this more generically:

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

Even so, if you used a variable, it would probably have to be decorated:

append:dup.(n + 1) [a b c] [d e]

append:dup.@n [a b c] [d e]

The relatively "noiseless" injection to the head of the args list feels like a better answer to me:

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

append:dup n [a b c] [d e]

Actually, this was what I wrote first… but then I remembered that CHAIN! binds tighter than TUPLE!, so this wouldn’t make sense. Or am I misremembering? If this can be done, I think it looks better than using CHAIN!.

TUPLE! is the lowest (giving the pleasing hierarchy in terms of "heft"...lighter things are tighter).

One of the problems with putting arguments into the chain/tuples is that binding gets tricky. There's some open issues with that, but if you embed arguments into a sequence then you're tying their binding up in the binding of that sequence. It's still a ways out.

I think I still would prefer the "configuring" refinements to put their arguments earlier, it really seems to be preferable in practice.

>> join:with '+ fence! ['a 'b 'c]
== {a + b + c}
1 Like

As a counterexample... there's DO:ARGS

do %some-script.r

do:args %some-script.r ["hello" "world"]

That particular case has a possible workaround, that since DO is no longer used to evaluate blocks, the args could go in a block as part of a "do spec"

do [%some-script.r, args: ["hello" "world"]]

I don't know, but I thought I'd point out the first example I'd noticed where injecting the argument first wasn't ideal.

Of course, these cases could do what we have to do with configuring refinements that aren't where we want them today, which is to use APPLY

do // [%some-script.r :args ["hello" "world"]]

(Note: I'm still wondering if APPLY should use SET-WORD!s instead of ":refinements", there are tradeoffs for each...)