APPLY II: The Revenge!

...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 Is An Error

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

Refinements Must Be Followed By A Non-Refinement

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

But you can pass refinements as arguments to refinements...just use a quote!

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

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

No-Arg Refinements Permit LOGIC! But Set NULL or #

Remember: the DO FRAME! mechanics do not change anything, besides ~unset~ isotopes being turned to NULLs. So if a refinement doesn't take an argument, the only legal values for that refinement in the frame are # and NULL.

But APPLY isn't DO FRAME!. It's a higher level thing that builds a frame from the values you supply, and then has an opportunity to look over them before running DO FRAME!. So if it sees you gave a #[true] or a #[false] to a refinement with no argument, it will adjust it appropriately.

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

# = apply :testme [/refine #]
null = apply :testme [/refine null]

# = apply :testme [/refine true]
null = apply :testme [/refine false]

^META Arguments Are Also Accounted For

For those following the profound design points, the DO FRAME! mechanic does not allow you to have isotopes in frame slots. The way you get isotopes through to a function is through meta parameters, and by convention those parameters are quoted or otherwise "leveled up" into non-isotope status.

But as another convenience, APPLY detects when a parameter is meta and will level it up...because the low-level frame mechanics aren't allowed to editorialize:

>> non-detector: func [arg] [arg]  ; not a meta argument, isotopes illegal

>> apply :non-detector [~baddie~]
** Script Error: non-detector needs arg as ^META for ~baddie~ isotope

>> detector: func [^arg] [arg]

>> apply :detector [~baddie~]
== ~baddie~

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:

What's Next?! Making It Easier To Use!

Imagine if we let <- be an infix operator...taking the name of the function to apply on the left, and a block on the right:

<-: enfix func [
    'action [word! tuple! path! group!]
    args [block]
][
    apply (if group? action [do action] else [get action]) args
]

It's rather slick!

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

>> append/only <- [[a b c] [e f] /dup 2]
== [a b c [e f] [e f]]

Of course, you can mix it up with your own freaky ideas, even variadic ones!

>> $: enfixed func ['name [word!] 'args [<variadic> <end> any-value!]] [
    args: make block! args
    apply :(get name) args
]

>> data: [a b c]

>> (append $ /dup 2, data [d e])

>> print ["data is" mold data]
data is [a b c d e d e]

The choice is up to you. Which is what all this is about!

2 Likes

A post was merged into an existing topic: Design Issues for New APPLY

Imagine if we let <- be an infix operator...taking the name of the function to apply on the left, and a block on the right:

I think it's worth trying.

I've been giving it a shot, and trying out other ideas too.

I tried colon, which looks pretty good in isolation:

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

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

But it kind of gets lost in the noise of all the other usages of colon:

thing: append : [series :value]

For most anything you'd put in that position, it struggles with the question of "why would it mean APPLY". Even if & wasn't hideous (and it is), there's no real tie-in that it has to other uses of & we might imagine...so having it do function application just looks random:

thing: append & [series :value]

What <- has going for it is that -> is the lambda function generator. So it looks like it's in the family of "function things". Pointing right creates a function, pointing left applies it.

>> (x -> [print [x + 20]]) <- [1000]
== 1020

So I think it's basically the best option. But...again...configurable for those who want to think differently!

1 Like

Mulling this has led me to a kind of seemingly inevitable conclusion...

I think this should be the behavior of terminal slash

Here it is demonstrated with one of rebmake's "that-sure-is-a-lot-of-refinements" functions:

add-project-flags/ [
    ext-objlib
    /I app-config.includes
    /D compose [
        (either ext.mode = <builtin> ["REB_API"] ["REB_EXT"])
        (spread app-config.definitions)
    ]
    /c app-config.cflags
    /O app-config.optimization
    /g app-config.debug
]

The slash marks a kind of barrier between the function and its block argument. It's like it's saying "whoa, hold up now, you're not actually taking that BLOCK! as a parameter!"

  • This avoids what's upsetting about a quoting apply add-project-flags [...] syntax. It may "look" nice on the surface, but you have to scan backwards to see "oh, there's an APPLY back there"

  • Non-quoting apply :add-project-flags [...] has the reverse problem: you think application is being suppressed and have to scan backwards to the APPLY and see "oh, no it's not, it's actually running".

Terminal slash kills two birds with one stone: it keeps things in the range of function application (e.g. APPLY/ONLY has a slash and that's connoted with application), and gives the visual separation we want at the location we want it.

This bakes it into the evaluator a bit more (though I'm proposing evaluator hooks that parallel UPARSE's combinator MAP! to permit varying it).

But the proof of concept that you could write a function doing it has been done. You can write this form of APPLY yourself with FRAME!, and design other APPLY-like operators. Just because you can write an apply operator doesn't mean that the evaluator has to avoid giving a foundational notation to this very critical behavior.

This will mean rethinking the other applications for terminal slash that are in the mix right now...

In The Evaluator, FOO/ Has Been A "This is a Function" Annotation

I had it so that if you terminated something with a slash it would run it...but warn you if it wasn't an action:

 >> print/ "Hello"
 Hello

 >> mystery: 10

 >> mystery/ "Hello"
 ** Error: MYSTERY is not an action!, can't run with terminal /

That's been mostly a means of commenting. A parallel concept I had was that you could ensure things where actions by assigning to paths with terminal slash:

>> x: 10

>> foo/: :print
== #[action! [line]]

>> bar/: x
** Error: Can't assign non-ACTION! using terminal slash

Terminal Slash Has Also Retriggered ACTION! Values

Today, this behavior is useful with GROUP!

 >> (either true [:print] [:elide])
 == #[action! [line]]

 >> (either true [:print] [:elide])/ "Hello"
 Hello

This becomes more useful with the ACTION!-vs-isotopic-ACTION! distinction. That's because plain WORD!s referring to actions wouldn't run in the evaluator. So foo/ could be a shorthand for run foo when FOO contained a plain inert action.

But...here's a thought: you probably really should be enclosing things in some kind of array anyway, if you're getting a function value somewhere:

 dump-block: func [block [block!] dumper [action!]] [
     for-each item block [
         print "Dumping item..."
         dumper item  ; what if dumper doesn't take exactly 1 argument?
         print "Item was dumped."
     ]
 ]

When reading some of BrianH's code when first looking at Rebol, I'd notice in cases like this the calls would be in GROUP!s, e.g. (dumper item). It would quarantine the function.. and sometimes it would take advantage of the fact that if the function took fewer parameters, that would be tolerated (saying it just wasn't interested in those).

But using dumper/ [item] instead of (dumper item) has advantages, such as that you won't be impacted by parameter conventions like quoting. So there's no way for the function you are calling to get the name of the variable you're passing--for instance.

In UPARSE, a FOO/ Has Been Running the ACTION! combinator

The implementation is a bit convoluted at the moment. But that it works at all as a usermode-implemented construct is pretty impressive:

  >> parse [1 2 3] [collect some [
       keep negate/ integer!
  ]]
   == [-1 -2 -3]

This is not necessarily contentious--given that you are free to kick over into a GROUP! if you want a function invocation that doesn't draw arguments out of the parse stream. But it could also be done by a RUN combinator that just picked up whatever came next.

  >> parse [1 2 3] [collect some [
       keep run negate integer!
  ]]
   == [-1 -2 -3]

And we could carry over the apply behavior in case you wanted a midstream application:

  >> parse [[a b] 2 "hello"] [
         series: <any>, n: integer!, append/ [series [c d] /dup n]]  text!
     ]
  == "hello"

  >> series
  == [a b [c d] [c d]]

At Time Of Writing, Terminal-Slash-APPLY Looks Like a Winner

I found that <- was no good for me, nor was //.

I think the other purposes terminal slash has been used for can be rethought, certainly for such a big payoff.

There'd still be the option of APPLY if you want it, but I'm now pretty firm that it shouldn't quote.

1 Like