Show the Love for SHOVE

>-

Despite having a bit tricky to implement, it's very easy to understand. It just pushes the argument on its left to be the first argument of the operation on its right:

>> 1 >- add 2 * 3
== 7  ; as if you'd written `add 1 2 * 3`

Simple, right? You might think it's not something you would use... but you'd be thinking wrong!

Invoke enfix functions in modules or libraries

Enfix execution cannot work with paths (this isn't a bug, it shouldn't work):

r3-alpha>> 1 lib/+ 2 * 3
** Script error: op! type is not allowed here

But shove has your back:

>> 1 >- lib/+ 2 * 3
== 9  ; as if you'd written `1 + 2 * 3`

(Note the result is different from what you get with add, since + is enfix.)

This is only going to get more critical as modules and namespacing move forward. And anyone who's redefined an operator--but wants to temporarily reach for another implementation--will want this.

Invoke refinements on enfix executions

Historically there aren't really refinements on infix functions, because you'd never be able to use them. Now you can:

>> ++: enfixed func [a b /double] [a + either double [2 * b] [b]]

>> 10 ++ 20
== 30

>> 10 >- ++/double 20
== 50

In the common case, you hope that your operator doesn't need refinements. But when a special case comes up, it's nice to not have to define a prefix form just for that one instance. (which means coming up with a new name that people need to know also). Now you can take for granted that shove is there when you need it.

Symbol-y, but not symbol soup: it communicates!

Being an "arrow word" is crucial to showing its disruption of the evaluation order. That's not going to be conveyed by a short alphabetic name:

>> 1 shove lib/+ 2 * 3   ; lame and wordy
== 9

Invoking enfix functions from modules/namespaces and using refinements with them is fundamental behavior. So it needs to be very short and light. You want people to take this thing for granted!

Extra credit: tweak the argument precedence

Rebol is well known for having a difference in interpretation when you use an infix operator vs. not:

>> add 1 2 * 3
== 7

>> 1 + 2 * 3
== 9

(If you don't know why that is--and wonder why such a "quirk" hasn't been changed despite a lot of thought--you can read more than you ever wanted to about it).

Above I showed that shove takes its cue on how to act based on the enfixedness of what you're shoving into:

>> 1 >- lib/add 2 * 3
== 7  ; as if you'd said `add 1 2 * 3`

>> 1 >- lib/+ 2 * 3
== 9  ; as if you'd said `1 + 2 * 3`

But it might be argued that since >- is enfix, the overall operation is always enfix, which intuitively would suggest both of the above should be 9.

It's tough to say. But I liked the simple definition of "shove uses the value on the left as the first argument". Imagine what the "as if you'd said" would have to be if the shove into ADD above gave 9, if you couldn't take for granted another known another name for the operation (like +):

>> 1 >- lib/add 2 * 3  ; let's say it's the hypothetical enfix-always version
== 9  ; as if you'd said `temp: enfixed :add | 1 temp 2 * 3`

But for sake of completeness...I introduced "force enfix behavior (->-)" and "force prefix behavior (>--)" forms:

>> 1 ->- lib/add 2 * 3
== 9  ; as if you'd said `1 + 2 * 3`

>> 1 >-- lib/+ 2 * 3
== 7  ; as if you'd said `add 1 2 * 3`

I think it's cute that ->- looks like a picture of args on the left and right of an operator, suggesting infix... while >-- looks like no args on the left with two args on the right!

But you could also just use parentheses:

>> (1 >- lib/add 2) * 3
== 9

>> 1 >- lib/+ (2 * 3)
== 7

So whether ->- and >-- survive or not is an open question. If anyone actually uses them, I'd be interested to know.

2 Likes

You always manage to amaze. The only downside is, that it gets harder and harder to keep track of all the new features.

1 Like

So the 2019 idea of SHOVE... got a bit out of control.

It was at one point used to implement ME and MY:

>> some-variable: 10

>> some-variable: me + 20
== 30

The idea was that ME would take its left hand side literally, and then be variadic on the right to [+ 20]. It would GET the variable, then pass it to SHOVE, along with the variadic feed on the right.

Binding bugs in the too-complicated shove code led to scrapping that and expressing ME and MY as macros:

me: enfix macro [@left [set-word! set-tuple!] @right [word! tuple! path!]] [
    return reduce [left, plain left, right]
]

my: enfix macro [@left [set-word! set-tuple!] @right [word! tuple! path!]] [
    return reduce [left, right, plain left]
]

So that just turns [some-variable: me +] into [some-variable: some-variable +]

And [some-variable: my append] becomes [some-variable: append some-variable]

(Though I suppose technically, there's no reason ME needs to enforce an argument on the right... (some-variable: me) could be legal, although useless.)

Anyway, Without ME and MY, SHOVE Had No Uses

Once ME and MY no longer used SHOVE, the only place it appeared was in tests, that broke often and caused me grief.

Due to a recent change they're all completely broken

At frustrated moments, I've wanted to delete it completely. But... I'll try to not be hasty, and keep any useful parts.

Enfix No Longer A Necessary Applicatoin

So this isn't going to be an issue SHOVE needs to solve any longer, thanks to The Big Alien Proposal :flying_saucer:

You will be able to freely use enfix operations with refinements, because they will be done with CHAIN!s, and the head of the chain can be tested for enfix in a way that's no more or less broken than testing a word for enfix.

This might not seem too palatable for operators:

 a =:strict b

But not so useless: what about things like ACCESSOR? These take a SET-WORD! on their left, and I can reasonably see them taking refinements.

a: alias:weak $var

Anyway, you won't need shove to give refinements to enfix functions anymore.

So the most logical implementation of this is to have SHOVE take one unit of material on the left as a hard literal... and then decide what to do with it.

But if it takes its left as hard (or even soft) literal, then consider this:

>> 1 + 2 * 3
== 9   ; e.g. (1 + 2) * 3

>> 1 + 2 >- lib/* 3
== ???

The nature of literal arguments means that >- will out-prioritize the 1 + on the left, and act like:

>> 1 + (2 >- lib/* 3)
== 7

So what are the options, here?

  1. The main version of SHOVE (>-) takes its left hand side as a hard literal, and then treats the parameter how the right hand side wants (hard literal, soft literal, evalative). A second version of SHOVE (->-) takes its left hand side evaluatively...and either does not work with functions that take their first argument literally, or gives them the evaluative product (like an APPLY would).

  2. Flip that around: the main version of SHOVE (>-) takes its left hand side evaluatively, and you use a special version to take the left hand side literally.

  3. Say screw it: SHOVE isn't used that often and you should get used to the fact that it may take the left more tightly than you're used to. Be glad you have it at all, and just write (1 + 2) >- lib/* 3 if getting 9 out is so important.

  4. Ignore reality and try to make one native that incoherently attempts to serve all the purposes and then is specialized as three distinct operators.

For reasons that elude me now, in 2019 I went with [4].

From where I stand today, [3] is the obvious answer. Think about it: if people are going to have to modify their callsite, what's lower cost:

  • to teach them what a new alternative operator is called and how it works, and add one or more symbols to the callsite to get it... or...

  • to have them add exactly two symbols of ( and ) which they already understand fully?

Given the new string proposal for using -{...}- and --{...}-- etc. for strings (alongside -"..."- and --"..."--), it seems to me that -<...>- is going to make more sense as a form of TAG! which allows embedded < and >, as well as spaces at the head and tail.

tag: -<
    My modern multi-line tag,
    which can have > and < in it
>-

So SHOVE will need another syntax. ->- is still available, I guess.

>> 1 + 2 ->- lib/* 3
== 7

Thanks to TAG!, arrow words are pretty scarce. I hate to use something important like <- or >> or >>= for something this niche.

I'll mention that I still have a vague wish for >> as a "console operator", for some kind of wild trick that enables copy and paste replaying of console transcripts.

Anyway, I'll go with ->- for now, just to get things moving.

Here's some of the fever dream of the previous >-- and ->- variations, allowing a degree of freedom that's too subtle for anyone to wield usefully:

; >- is the SHOVE operator.  It uses the item immediately to its left for
; the first argument to whatever operation is on its right hand side.
; Parameter conventions of that first argument apply when processing the
; value, e.g. quoted arguments will act quoted.
;
; By default, the evaluation rules proceed according to the enfix mode of
; the operation being shoved into:
;
;    >> 10 >- lib/= 5 + 5  ; as if you wrote `10 = 5 + 5`
;    ** Script Error: + does not allow logic? for its value1 argument
;
;    >> 10 >- equal? 5 + 5  ; as if you wrote `equal? 10 5 + 5`
;    ; nothing, e.g. branch trigger
;
; You can force processing to be enfix using `->-` (an infix-looking "icon"):
;
;    >> 1 ->- lib/add 2 * 3  ; as if you wrote `1 + 2 * 3`
;    == 9
;
; Or force prefix processing using `>--` (multi-arg prefix "icon"):
;
;    >> 10 >-- lib/+ 2 * 3  ; as if you wrote `add 1 2 * 3`
;    == 7
;
>-: enfix :shove
>--: enfix specialize get $>- [prefix: 'yes]
->-: enfix specialize get $>- [prefix: 'no]

It turns out there was some odd postponing variant too:

|>: runs tweak enfix copy get $shove 'postpone 'on

I cannot imagine when that would be useful:

>> x: add 1 add 2 3 |> lib/* 4
== 24

Certainly there's a better idea for what to do with that "right flag" operator. (I probably didn't think it was useful, just testing the postpone feature mixed with shove.)

Old Tests

Going to rework these, not sure which of them are actually coherent.

; NORMAL parameter
;
(9 = (1 + 2 ->- multiply 3))
(7 = (add 1 2 ->- multiply 3))
(7 = (add 1 2 ->- (:multiply) 3))

; :HARD-QUOTE parameter
(
    x: null
    x: ->- default [10 + 20]
    x: ->- default [1000000]
    x = 30
)

; SHOVE should be able to handle refinements and contexts.
[
    (did obj: make object! [
        magic: enfix lambda [a b /minus] [
            either minus [a - b] [a + b]
        ]
    ])

    ~???~ !! (1 obj/magic 2)  ; must use shove

    (3 = (1 ->- obj.magic 2))
    (-1 = (1 ->- obj.magic/minus 2))
]


; PATH! cannot be directly quoted left, must use ->-

[
    (
        left-the: enfix :the
        o: make object! [i: 10 f: does [20]]
        ok
    )

    ('o.i = o.i left-the)
    (o.i ->- left-the = 'o.i)

    ~literal-left-path~ !! (o/f left-the)
    (o/f ->- left-the = 'o/f)
]


; Right enfix always wins over left, unless the right is at list end

((the ->-) = first [->-])
((the ->- the) = 'the)
('x = (x >- the))
(1 = (1 ->- the))

(1 = (1 >- the))
('x = (x >- the))

; "Precedence" manipulation via >- and ->-

(9 = (1 + 2 ->- multiply 3))
(9 = (1 + 2 >- multiply 3))
(9 = (1 + 2 >-- lib/* 3))
(9 = (1 + 2 ->- lib/* 3))

(7 = (add 1 2 * 3))
(7 = (add 1 2 ->- lib/* 3))
(7 = (add 1 2 >- lib/* 3))

~expect-arg~ !! (10 ->- lib/= 5 + 5)
~expect-arg~ !! (10 >- lib/= 5 + 5)
(10 >-- lib/= 5 + 5)
(10 >- = (5 + 5))

~no-arg~ !! (
    add 1 + 2 >- multiply 3
)
(
    x: add 1 + 2 3 + 4 >- multiply 5
    x = 38
)
(-38 = (negate x: add 1 + 2 3 + 4 >- multiply 5))

~no-arg~ !! (
    divide negate x: add 1 + 2 3 + 4 >- multiply 5
)
(-1 = (divide negate x: add 1 + 2 3 + 4  2 >- multiply 5))

; Predicates allow the specification of an additional constraint, which if
; not met, will also lead to defaulting.
(
    x: "not an integer"
    x: >- default/predicate [10 + 20] :integer?
    x = 30
)(
    x: 304
    x: >- default/predicate [10 + 20] :integer?
    x = 304
)

So as it turns out, the Rye language is centrally based on this idea...so much so that it builds in a symbol type for doing it, when there's a leading vertical bar.

Something in the vein of:

rye-like>> x: add 1 add 2 3 |lib/* 4
== 24

You could imagine paring the operator down to just be what vertical bar means:

rye-like>> x: add 1 add 2 3 | lib/* 4
== 24

But I don't particularly like that. And the style of programming which this is supposed to be useful for is done better--I believe--by something like FLOW.

So I don't know if I want to add |> back for this meaning, vs something else, or just not have anything that means this in the box.