APPLY II: The Revenge!

I'd said "It's time to bring back APPLY".

...and by "it's time" I apparently meant "within the next year, maybe?"...

But better late than never, right? It's in!

Refinements Can be Provided In Any Order

[a b c d e d e] = apply append/ [[a b c] [d e] dup: 2]
[a b c d e d e] = apply append/ [dup: 2 [a b c] [d e]]
[a b c d e d e] = apply append/ [[a b c] dup: 2 [d e]]

[a b c d d] = apply append/ [dup: 2 [a b c] [d e] part: 1]
[a b c d d] = apply append/ [[a b c] [d e] part: 1 dup: 2]

Any Parameter (Not Just Refinements) Can Be Used By Name

Once a parameter has been supplied by name, it is no longer considered for consuming positionally.

[a b c d e] = apply append/ [series: [a b c] value: [d e]]
[a b c d e] = apply append/ [value: [d e] series: [a b c]]

[a b c d e] = apply append/ [series: [a b c] [d e]]
[a b c d e] = apply append/ [value: [d e] [a b c]]

Commas Are Ok So Long As They Are Interstitial

[a b c d e d e] = apply append/ [[a b c], [d e], dup: 2]
[a b c d e d e] = apply append/ [dup: 2, [a b c] [d e]]

>> apply append/ [dup:, 2 [a b c] [d e]]
** Script Error: end was reached while trying to set dup:

Giving Too Many Arguments Defaults To An Error

>> apply append/ [[a b c] [d e] [f g]]
** Script Error: Too many values in processed argument block of APPLY.

If you want, you can ask it to :RELAX

>> apply:relax append/ [[a b c] [d e] [f g]]
== [a b c [d e]] 

SET-WORD Must Be Followed By A Non-SET-WORD

>> apply append/ [dup: part: 1 [a b c] [d e]]
** Script Error: end was reached while trying to set dup:

But you can pass SET-WORD as arguments to refinements...just use a quote! (or a $ if you want to bind it)

>> tester: func [:refine [any-value!]] [refine]

>> apply tester/ [refine: 'ta-da!:]
== ta-da!:

No-Arg Refinements Permit OKAY and NULL

Remember: the EVAL FRAME! mechanics do not change anything. So if a refinement doesn't take an argument, the only legal values for that refinement in the frame are OKAY and NULL.

>> testme: func [:refine] [refine]

>> apply testme/ [refine: okay]
== \~okay~\  ; antiform

>> apply testme/ [refine: null]
== \~null~\  ; antiform

>> apply testme/ [refine: 1020]
** Error: No-Arg refinements can only be ~okay~ and ~null~ antiforms

^META Arguments Are Accounted For

APPLY detects when a parameter is meta and handles it for positional arguments:

>> non-detector: func [arg] [arg]  ; not a meta argument, no unstable antiforms

>> apply non-detector/ [pack [10 20]]
== 10

>> detector: func [^arg] [^arg]  ; unstable antiforms allowed

>> apply detector/ [pack [10 20]]
== \~['10 '20]~\  ; antiform

I know not everyone has gotten their heads around isotopes yet, but they are critical... this stuff was the missing link to making it all gel.

:dizzy: :dizzy: :dizzy:

2 Likes

6 posts were split to a new topic: Naming The Infix APPLY Operator

A post was split to a new topic: Critiquing Red's Updated APPLY Implementation

And now APPLY has an infix shorthand...

Meet The // Operator!

The choice to use slashes for the operator became obvious, now that /WORD Runs Functions

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

>> append // [dup: 2 [a b c] spread [e f]]
== [a b c e f e f]

>> append:dup // [[a b c] [e f] 2]
== [a b c [e f] [e f]]

It's strange but also it's a mixture of heavy and light, as a kind of "joiner" or concatenator, almost as if you were sticking the things together into a single thought.

append//[[a b c] <d> dup: 2]

I know it's not perfect, but nothing will be. I don't like an APPLY operator that sits to the left and quotes:

apply append [[a b c] <d> dup: 2]  ; !!! bad

Because that makes it look too much like the BLOCK! is the first argument to APPEND. So you really have to do APPLY with an inert form:

apply get $append [[a b c] <d> dup: 2]

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

So if quoting is in play, there has to be something learnable and infix to jolt the flow. I've tried a lot of things at this point, this feels like the logical conclusion.

append // [[a b c] <d> dup: 2]
1 Like

Follow-Up: APPLY And Parameter Conventions

Historical APPLY has not taken parameter conventions into account.

So for instance: if you have a literal parameter (denoted by a quote mark) that has not been taken into account by the APPLY operation.

Should it? It seems like maybe it should, so that foo // [baz bar] would be a synonym for foo baz bar, and maybe there should be a refinement to APPLY which lets you say not to take the parameter convention into account.

That may make sense for literal parameters, but what about ^META parameterization?

It could be based on whether you use ^arg: or arg: or no label at all.

For example:

metathing: func [^arg [pack! integer!]] [  ; sees packs, or plain integers
    probe arg
]

>> metathing // [pack [1 2]]
~['1 '2]~  ; anti

>> metathing // [arg: pack [1 2]]
1

>> metathing // [^arg: pack [1 2]]
~['1 '2]~  ; anti

How big a deal is this? Errrrm. Well, lifted refinements are rare (incredibly so right now, as they're not legal and none have ever been made).

The Same Issue Applies to SPECIALIZE

Right now, SPECIALIZE is very lax. It binds the block of specializing code into the frame it is specializing. You can run loops or do whatever you want. This is not like APPLY... which does its own dialected interpretation of the block.

If SPECIALIZE runs generalized code in the frame, you would have to use lifted assignments to set the fields (^xxx: ...). If it was just dialected and mandated you write the code as [arg1: ... arg2: ...] then it might try the strategy I'm suggesting, where undecorated assignments decay, and ^arg: assignments did not. (This would present a thorn for specializing with a raised error, since a strict format would have nowhere to put a TRY...)

This is a tough call. I can see uses for both kinds of operations, and that goes for APPLY as well (I called the run-arbitrary-code-version "APPLIQUE").

It would be nice if you could SPECIALIZE based on just order of args without naming them, like specialize add/ [5]... perhaps even leveraging trash to skip slots as specialize subtract/ [~ 10] (this starts to tread on the turf of POINTFREE, but that is proposed as a more complex operation that can do more than one layer of specialization).

(As a quick aside, I'll mention that maybe it would be possible to bind things like 1: and ^2: such that they could be used in SPECIALIZE or APPLY to refer to that Nth argument. I can imagine ways this could work, but at this point it's just imagination.)

Common Case Likely Favors Dialected Interpretation

Experience dictates that the most common uses of specialize are things like:

first: specialize pick/ [picker: 1]

It seems like a shame for it to be any worse than that. Further, in this dialected interpretation I know how to make things like 1: and ^2: work--it's trivial because there's no binding in play, you're just walking a block and filling in a known FRAME!, grabbing a SET-XXX and then doing one step of eval each time. This also means you could write something like:

picker: 10
first: specialize pick/ [picker: picker - 9]

...since there's no binding needed for the frame fields, you can assume each expression being evaluated is bound in the active environment.

So what to call the version that binds a frame, and makes you use ^picker: but lets you write more freeform code? It could be specialize:freeform or specialize:code or something, shorthanded as specialize*. Then APPLY would have a parallel notation (APPLIQUE was never meant to stick around...)