Design Issues for New APPLY

UPDATE 2021/2022: These issues are now all solved: APPLY II, The Revenge!

And we don't have to worry about APPLY taking its function argument quoted. Because if you want it to look clean, you don't want the word APPLY in there at all. This is now the behavior of terminal slash paths!

  append/ [series value /dup 2]

In any case--APPLY does not quote, and "Worldview #1" below is the winner.

First Big Question: Quote The Function or Not?

This has been on my mind since the beginning: should you have to use a GET-WORD! to name the function to apply, or does it quote by default...and make you put code fragments that returns a function in a group.

 ; Worldview #1
 apply :append [series value /dup 2]
 apply second reduce [:insert :append] [series value /dup 2]  ; will APPLY :APPEND

 ; Worldview #2
 apply append [series value /dup 2]
 apply :append [series value /dup 2]  ; still legal if you want
 apply (second reduce [:insert :append]) [series value /dup 2]  ; GROUP! required

We have at least some relief at not having to consider Worldview #3, of apply 'append [series value] so APPLY can capture the name "append" for debug stack purposes. That's not needed because :append gets an ACTION! that carries the label in its cell. This has been a change that punches above its weight.

In the beginning I would have said it definitely has to go with Worldview #1, because in the second case you don't get a clear signal that APPEND is not being called on [SERIES VALUE /DUP 2] and the result being passed to APPLY. If you didn't see the apply or were otherwise distracted, your reading comprehension would be thrown off by breaking from convention.

Over time I've become more of a believer that the whole premise of the language is to let you bend constructs to predominate use, so the 99% case is pleasing to use. The now famous case of OF's left-quote, for instance:

type: 'type of x  ; without OF quoting

type: type of x  ; with OF quoting

The quote mark makes it clearer, but when OF becomes so common that you "just know" then it's a much more pleasing function to use. How many times do you use OF with a reflector you pick via code? When is that code a single-arity function you wouldn't have to put in a GROUP! to use with enfix anyway??!

But does that same philosophy to APPLY, that we come to read apply append as one indivisible unit, and feel thankful for not having to reach for the colon key? It's a little bit different than OF because you read left to right, and you can start reading at the APPEND with no speedbump after it.

I'll stress that you would still have the choice. If you were the type of person who thought it clearer to use the colon, you could do so. You just wouldn't be able to invoke a function-to-generate-the-function unless you put that in a GROUP!.

I can't make up my mind. So I think I'm going to make the answer Worldview 1.5:

 ; Worldview 1.5
 >> apply second reduce [:insert :append] [series value]
 ** Error: APPLY arguments must be GET-WORD! or GROUP! at the moment

This leaves the door open to interpreting plain WORD! as naming a function in the future. And it gives time for people to find a really compelling case for where not being able to generate a function without using a GROUP! would be a deal-breaker.

Naming Refinements

Historical APPLY had a nasty positional nature for refinements, which led to things like this, from the R3-Alpha module code:

return map-each [mod ver sum name] source [
    apply :load-module [
        mod true? ver ver true? sum sum no-share no-lib import true? name name delay
    ]
]

You see the combination of true? ver followed by ver as filling in first the refinement slot for /VERSION, and then the value of the refinement argument.

Then you see "used or not" refinements being supplied to their argument cells by the value alone (no-share signaling not to provide /SHARE, no-lib signaling not to use /LIB)

But we can now put refinement names in the APPLY dialect!

apply :load-module [
    mod /version ver /sum sum
        /share null /lib null  ; avoids need for "clarifying" `no-share: false`
        /import import /name name
        /delay delay
]

You might ask what was stopping that before. The issue was that historical Rebol had the problem that if you had an argument that could take on any value...including NONE!...then NONE! could not be used to represent the state of the refinement itself.

(Imagine if you are specifying an initial value to fill a block with (as in ARRAY/INITIAL). It may be important to distinguish putting a NONE! in vs. not having a default at all.)

This is solved in Ren-C by reserving the non-valued NULL state for "refinement not taken".

Note: I'd gone back and forth a while over whether a REFINEMENT-like PATH! would be appropriate to use in this dialect, based on the question of whether they would be evaluator-active or not. Ideas were floated around like foo would fetch a variable but be an error if it was NULL, while /foo would fetch a variable and allow NULL.

In the end, inertness for refinements won out. BLANK!-headed PATH!s and TUPLE!s simply have more dialecting power if they don't overlap what could be a legitimate user intention to fetch variables. This APPLY situation shows a good example of why inertness is good.)*

Also, if you want to literally pass a refinement value you can do so. Just quote it:

   apply :whatever [/refinement '/refinement]

That will pass /REFINEMENT as the refinement parameter.

Providing Normal Arguments By Name

A neat feature of new APPLY will be not only that you can reorder refinements to put them in any position, but also that you can name normal arguments.

So things like this would be legal:

>> apply :append [/dup 2, /value <d>, [a b c]]
== [a b c <d> <d>]

Everything is streamlined by the idea that refinements represent one (and only one) argument in the frame. Which has been a critical and elegance-making change.

The concept is that ordered parameters which have not yet been named would be filled in, in the order that they are supplied. You wouldn't be able to specify something twice, so this would be an error:

>> apply :append [/series [a b c], [d e f], <g>]
** Error: Duplicate argument supplied for SERIES

Can LOGIC! Be Used With Parameterless-Refinements?

I have belabored the point of why we don't want low-level FRAME! mechanics getting involved in transforming [#[true] #[false]] into [# null] for refinements that are "either used or not".

Those rules don't really "apply" here. APPLY is its own dialect with its own mechanics, and it can do what's convenient. It can canonize true and false into # and NULL in the frame.

foo: func [/usable /logicable [logic!]] [...]

apply :foo [/usable 1 < 2, /logicable 1 < 2]
; this would give usable of `#`, and logicable of `#[true]`

apply :foo [/usable 1 > 2, /logicable 1 > 2]
; this would give usable of NULL, and logicable of `#[false]`

Hopefully it makes sense why I think APPLY should be able to do this, while MAKE FRAME! and friends don't. MAKE FRAME! is the tool for writing APPLY-like-things, and there's no layer between the frame and the evaluator where it's appropriate to put these transformations. It should be kept pure.

Should You Be Able to Provide Refinements By Position?

I'm not sure if giving refinements by position is a great idea. But this hits on a philosophical question of whether refinements have an order that is exposed to the user.

But specialization can shuffle the order. For instance, what was once a refinement can become a normal argument.

>> foo: func [x [integer!] /refine1 [integer!] /refine2 [integer!]] [...]

>> parameters of :foo
== [x /refine1 /refine2]  ; one normal argument

>> foo-2: :foo/refine2
== [x refine2 /refine1]  ; two normal arguments (e.g. acts like FOO/REFINE2)

This makes things a bit wacky.

I feel like the safest decision for right now is to say that once you've filled all the ordered arguments, any more arguments in the APPLY with no label causes an error. The place where this would cause issues would be if BLOCK!s are used to serialize and deserialize frame state generically. That's a thorny problem in general, which brings us to...

Should There Be An APPLY/ONLY

The idea behind APPLY/ONLY was that you have the values for a function's arguments already processed, and you don't want to evaluate them again.

One thing that breaks APPLY/ONLY in Ren-C's world is that this doesn't give a way to supply NULL arguments. One option would be to let the BAD-WORD! with label ~null~ be an escape mechanism for this, which might cover a lot of cases (though obviously not accurately capturing a frame that purposefully wanted to pass the ~null~ BAD-WORD!).

Because FRAME!s have come along as a general and pure tool for representing function applications, the pressure is taken off of APPLY to be all things to all people. I think it should start out as the handy evaluative form, and then wait for motivating scenarios before trying to bend to anything else. Maybe another operator entirely would be more suitable, or just using FRAME! as it is.

2 Likes

I was debugging something in the not-really-used-but-still-there encapping code, and saw one of the many kind of lousy line-breaking situations:

    section-headers-data:
        read/seek/part file e_shoff (e_shnum * e_shentsize)

The code is indented enough that it would exceed the desired 80-column-limit to put that all on one line.

What I sometimes do with this to try to communicate is put the whole thing in a GROUP!:

    (section-headers-data:
        read/seek/part file e_shoff (e_shnum * e_shentsize)
    )

But it's crappy enough that I avoid doing it, as cases like this one show.

With the proposed design for APPLY, you'd have an alternative...with comma and line break options too:

    section-headers-data: apply :read [
        file /seek e_shoff /part (e_shnum * e_shentsize)
    ]

    section-headers-data: apply :read [
        file, /seek e_shoff, /part (e_shnum * e_shentsize)
    ]

    section-headers-data: apply :read [
        file
        /seek e_shoff
        /part e_shnum * e_shentsize
    ]

In the post above, I question whether apply read or apply :read is better. But maybe there's an even better angle if you want more readability.

It's A Common Desire...Should It Have a Foundational Syntax?

I want to take terminal dot to mean "this is not a function call", e.g. var. would error if var was a function. So terminal slash was the complement...to mean "this IS a function call".

But what if terminal slash meant "this is a function call... and it's also an apply form"

    section-headers-data: read/ [
        file
        /seek e_shoff
        /part e_shnum * e_shentsize
    ]

This wouldn't have to lose my concept that :var/ would be "get me the variable and ensure it's an action" meaning... though it would lose the plain syntax of foo/ x y z of "be sure this is a function and run it", you'd have to say foo/ [x y z] instead. Though when you think about it... if someone is obsessive enough to need assurance that foo is a function, might they also be obsessive enough to want to specify a precise number of arguments to it?

Seems Promising...

Anyway, an interesting thought. It could put a close to the "should APPLY quote" debate, and say it wouldn't, because you'd only use APPLY with functions coming from expressions if you had this form.

4 posts were split to a new topic: APPLY II: The Revenge!

On this note, it seems Red's APPLY is (maybe?) going for the case of "worldview 1", where you can say apply append [...] instead of apply :append [...]

apply.red · master · hi i am boris / Red mezz warehouse · GitLab

Boris says:

"Also, should function name be a lit-arg?
I expect the main use cases will have a literally written function name,
and where that is not enough, a paren could be used. E.g.:

apply (pick [func1 func2] condition) [...]

Lit-arg will also allow us to pass a function value to it without parens (though currently that's impossible since we can't use function value to construct a path)

apply :myfunc [...]

As a Reminder of My Opinion... "Worldview 1.5"

I guess maybe Red's usage gives the opportunity for them to be the guinea pigs. If it bites people then they can be the one to find out.

Gives me Thought: Might "APPLIQUE" Be Unified?

They're making it so you can use an object to apply parameters. If you have an already built object that can be used for a function in Ren-C, that has a name... it's called FRAME!, and you can just DO it. You don't need to APPLY it because it knows the name of the function it was created for.

So APPLIQUE is a version of APPLY that runs code bound into a frame.

>> flag: true

>> applique :append [
       series: [a b c]
       if flag [
           value: <control>
       ] else [
           value: <structures>
       ]
   ]
== [a b c <control>]

But APPLIQUE is a (deliberately) crappy name. I didn't want to take something that would be used for something else.

Should this be folded in as a variant of APPLY of some kind? APPLY/FRAME? APPLY/CODE? Or a different datatype?

>> apply :append [...]  ; act like normal APPLY

>> apply :append @[...]  ; act like APPLIQUE

In some sense what you want to say here is actually kind of like:

>> do make :append [
       series: [a b c]
       if flag [
           value: <control>
       ] else [
           value: <structures>
       ]
   ]
== [a b c <control>]

That would break it out into two steps; one step where you build the frame out of code, a bit like a MAKE OBJECT!.

But the problem here is that you're passing make an ACTION!, and getting a FRAME! back. :-/

Right now when you make a frame you say make frame! :append and all your parameters are taken. So the only way out of this looks like to allow MAKE taking an ACTION! instance in for "what to make" and giving you a FRAME! back. Which is weird.

Anyway, just mentioning because Red is using APPLY to mean two different things, though their division doesn't make sense in Ren-C's (quite superior :slight_smile:) model.

1 Like