Revisiting a Maybe-Not-So-Weird Old Idea: <- as IDENTITY

So at one point I had a kind of strange idea for what to do with <-.

It would just pass through what you gave on its right. And the idea was that you could use this in various situations as a kind of visual signal that you intended to produce a value that would be used by... something.

You could use it to hint when a line is being continued:

browse join
    <- https://github.com/metaeducation/ren-c/blob/master/CHANGES.md#
    <- unspaced [system.version.1 "." system.version.2 "." system.version.3]

That JOIN is arity 2. The arrows do nothing, but quickly pass through what they were passed.

The current alternative you might reach for are parentheses, but you can only put it around the outermost level, and it's noticeably inferior:

browse (join
    https://github.com/metaeducation/ren-c/blob/master/CHANGES.md#
    unspaced [system.version.1 "." system.version.2 "." system.version.3]
)

I had another idea that the arrow might be nice for signaling when you're at the end of a branch to help hint that the branch result is actually used.

 something: case [
     ... [...]
     ... [...]
     ; lots of code to where SOMETHING: has scrolled off the screen
     ... [
         your code here
         more code here
         <- append data "stuff"
     ]
 ]

It gives you a nice hint that the result of the append (the head of the appended series) is actually used.
But, these two uses are in contention. You can't use it to mean "continue the line above" and "pipe result out to some higher level. Perhaps this is better done with an impromptu CATCH/THROW?

 something: catch [case [
     ... [... throw ...]
     ... [... throw ...]
     ; lots of code to where SOMETHING: has scrolled off the screen
     ... [
         your code here
         more code here
         throw append data "stuff"
     ]
 ]]

(Note that CATCH/THROW have nothing to do with errors in Rebol, it's a lightweight construct for doing this kind of manipulation.)

Why Did I Kill Off The <- As Identity?

It was changed shortly after deciding to take -> for an infix lambda operation.

 foo: x -> [print ["I'm a lambda" x]]

 >> foo 1020
 I'm a lambda 1020

This is particularly nice-looking in branching when you want to pass the branch result.

all [
    1 < 2
    3 < 4
    #something
] then x -> [
   print ["X is" mold x]  ; X is #something
]

The first incarnation used => as JavaScript did. But not only is the arrow lighter, it doesn't weigh on the question of whether => is an "arrow", when <= is not one. (Debate has been had over whether => should be a synonym for >=, and =< a synonym for <=, etc. but status quo has won out.)

So <- was targeted as another function generator, namely for doing tacit programming as an alias for POINTFREE.

foo: <- [append [a b c]]

>> foo 'd
== [a b c d]

The attempt to write POINTFREE entirely in usermode stalled a bit in the face of other priorities, though it's still very much desired. And it doesn't seem like a bad idea to have <- and -> both connected as fundamental convenient function generators.

I've not really questioned the decision to strike <- as identity until I realized looking at some old code that it really is nice to have some alias for identity, used for one of the annotation purposes I mention.

Backtick is available, though backticks are kind of a blight in general:

browse join
  ` https://github.com/metaeducation/ren-c/blob/master/CHANGES.md#
  ` unspaced [system.version.1 "." system.version.2 "." system.version.3]

There's a few other ugly options, e.g. backslash which we still haven't given meaning to either.

browse join
  \ https://github.com/metaeducation/ren-c/blob/master/CHANGES.md#
  \ unspaced [system.version.1 "." system.version.2 "." system.version.3]

Anyway, the cool idea here was just that line continuation not be a scanner feature, but be signaled by means of a normal token that ran the identity function... and you'd be cued into realizing that it was a continuation just because there's no point in calling the identity function in source unless you were applying it like this.

We could throw in a little help by having the evaluator only allow this if it was--actually--an argument to a function.

Based on the above, I'm liking the backtick, and am not sure I'd want heavier for line continuation.

The idea of <- being an alternate way of saying identity that is used for the other annotation purpose (this result is used by the higher up stack level) would then be a possibility. The system could maybe help a little bit here too, by making sure the <- isn't an argument to a function...

POINTFREE would need some other shorthand. But maybe it's just an alternate mode of LAMBDA. Or maybe it doesn't really need a shorthand.

foo: ... -> [append [a b c]]

foo: <*> -> [append [a b c]]

foo: |-> [append [a b c]]

foo: -|> [append [a b c]]

foo: pointfree [append [a b c]]

>> foo 'd
== [a b c d]

Due to the change that enforces "strict mode" everywhere, you can't just write randomly into your module's global space (or script's global space, at this time scripts are isolated in the same way as modules but do new executions each time you run them and return a result that's not the module they are contained in)... you have to pre-declare things.

So this code broke:

if try first system.options.args [
    tests: local-to-file first system.options.args
] else [
    tests: %core-tests.r
]

One way to go about it would be to have a top-level declaration that you predeclare and write to it:

tests: ~
if try first system.options.args [
    tests: local-to-file first system.options.args
] else [
    tests: %core-tests.r
]

But that's a bit lame considering you could say:

tests: if try first system.options.args [
    local-to-file first system.options.args
] else [
    %core-tests.r
]

But here's where I think readability suffers a bit. Even in this short example, I feel that the fact that the result from LOCAL-TO-FILE is used isn't as obvious as it was. The longer the distance from that SET-WORD! to the expression the worse that disconnect gets.

This is why I advocated for:

tests: if try first system.options.args [
    <- local-to-file first system.options.args
] else [
    <- %core-tests.r
]

It would be completely optional. And in fact, you could make a good argument that it's not necessary on the second branch since you "know" it will be used, as it's not a function call with side-effects:

tests: if try first system.options.args [
    <- local-to-file first system.options.args
] else [
    %core-tests.r
]

Like other decisions in this medium, it's an aesthetic call, and you make the call you want.

The operator could do something I've talked about, which is be an operator that means complete fully. e.g. this could error:

tests: if try first system.options.args [
    <- local-to-file first system.options.args 10  ; FAIL: "extra stuff"
] else [
    %core-tests.r
]

So you could pick between:

foo // [arg1 arg2]
(<- foo arg1 arg2)

Eerily the same length. But, anyway, you get the idea.

It would be a little weird that -> is a function generator and <- would be the identity operator, but there is a sort of logic in terms of the direction of information flow.

I definitely consider this to be back on the table.


In the "Damn, Tripwires are Cool" Category

Here is what I did for now. :slight_smile:

tests: ~<https://forum.rebol.info/t/2165/2>~

if try first system.options.args [
    tests: local-to-file first system.options.args
] else [
    tests: %core-tests.r
]
1 Like

I strongly prefer this style. Or rather, this style but re-indented to make the structure clearer:

tests:
    if try first system.options.args [
        local-to-file first system.options.args
    ] else [
        %core-tests.r
    ]

Using indentation is interesting, but I do prefer:

tests: if try first system.options.args [
    <- local-to-file first system.options.args
] else [
    %core-tests.r
]

The nice thing is, you choose. Maybe from things you have gathered you might have your own insights to add to Fundamental Distinguishing Features of Rebol, if any have stood out.

But crucially, little is fixed. <- isn't...IF isn't. You can define IF as something totally different in the scope of a function, and that's fine if you have a good reason to do it...!