The role of "infix" (enfix) in Rebol

A radical proposal from @Brett suggests using an infix operation for property extraction.

So instead of LENGTH-OF FOO... or LENGTH? FOO... or the extremely unpopular LENGTH FOO, we'd use LENGTH OF FOO.

While it may seem surprising that this idea never came up before, it wasn't technically possible before. R3-Alpha was not able to have "OP!s" that quoted the left argument. At time of writing, Red can't do it either:

red> of: make op! func ['property [word!] thing [any-type!]] [
    print ["getting" property "of" thing]
]

red> length of "sigh"
*** Script Error: length has no value

red> 'length of "sigh"
getting length of sigh

In contrast, Ren-C has the technical mojo for infix OF. And my feeling is that this is a winner of an idea, and we pretty much have to do it.

But it starts to raise some questions just in general about the "philosophy of enfix" in Rebol. There are certain words in English that we do use infix, raising the question of if the corresponding functions in Rebol should be infix too. Words like AT, for instance...what makes more sense, at string 5 or string at 5? How about IN, should it be do in context [...] or do [...] in context?

When I asked this question of @earl way back when, his opinion was something along the lines of that Rebol bowing to infix was a practical issue. It created problems and made the language more irregular, and it shouldn't be used any more than it absolutely had to. He didn't want to see IN or AS or other operations becoming harder to work with by carrying the infix baggage.

But times have changed a bit. With the ability to complete an evaluation on the left side of an enfix operation (a "non-#tight" enfix), there's been new and exciting (well, I think so) innovations with things like ELSE and THEN. I have to say that now that I have ELSE, I feel like I can't go back. It has changed what Rebol looks and feels like, it's less stilted and more natural. And grotesque things like switch/default just fall away...

So I'm thinking that maybe we might want to enfix some operations that have been historically prefix. At the very least, we should make a list and weigh the issues. The die is cast now for OF, and that it has to be enfixed and left-soft-quoting. But how should we handle AS, AT, TO...? Any thoughts?

Yes, sounds like a good idea. How about map each, remove each and for each?

1 Like

Noted. That's definitely worth thinking about--exactly the kind of thing I feel like we should be brainstorming.

The first inklings of the idea for LENGTH/HEAD/TAIL/CONTEXT have been jammed into the wonky port/action mechanic (which I try and tidy up as I go along, while keeping things working):

Which is building more pressure on the lack of a good story for user defined datatypes or user defined actions (or as I've always wondered, what PORT!s even are). The pressure may be a good thing. We'll see.

While we're speaking about the "sanity check" department, one really has to wonder if one should push back against things that look like:

word: in lib word

That looks very foreign. But if you turn it around, it looks more natural:

 word: word in lib

There's a technical disadvantage in that if you want more than one expression's worth of lookahead on the left, you need to parenthesize. Yet the reality is that a lot of instances of IN are on words, like 'x in obj ... or on blocks, like do [some code] in obj. And enfix non-tight evaluation can have one completing expression to the left, so it's not a limit for word in lib or even to-word "foo" in lib.

I feel like the formulation of enfix has come to a sort of equilibrium of what might be possible within a certain range of lookahead in an evaluator. It might have some compatibility with the kind of sensibilities that guide natural language parsing...and it's something discounted by most evaluator-driven/stack-driven systems.

The doors may have cracked open a bit by saying people didn't have to write > 1 2 but could say 1 > 2, but that may have been just the beginning of how an evaluator might soften the "super-regular" world of a homoiconic language. Anyway, just reaffirming what I'm saying in this thread--it may be a place to look harder at.

One concern over an infix TO is how it would collect its first argument, as it only completes one expression. So:

 first back tail something to integer!

...is processed as...

 first back ((tail something) to integer!)

(unless TO is left-#tight like the math ops are, then it would be first back tail (something to integer!) but I'm not suggesting it be tight)

This might displease people who don't like parentheses or find it counterintuitive, as they could have previously written:

 to integer! first back tail something

@rgchris observed in chat that we might not fret so much about the potential for being forced to parenthesize the left of a TO by the TO-XXX specializations, which although asymmetrical could remain prefix (vs. postfix).

 to-integer first back tail something
 (first back tail something) to integer!

That's a thought. I don't know if people would expect postfix for consistency, e.g. (first back tail something) to-integer.

My other thought was just to have a good prefix fallback for TO. I can't think of a great one off hand, but a serviceable one could be CONVERT. It's a bit wordy and might be wanted for something else.

But an actual good alternate prefix form came up via @johnk was regarding the similarity of AS to CAST, and whether CAST could be the prefix.

 first back tail something as block!
 ;-- ...means `first back (cast block! tail something)`

 cast block! first back tail something
 ;-- ...means `cast block! (first back tail something)`

I know these ideas are a little "weird-seeming" but I do want to keep pushing on them. While we saw Rebol variants like Boron try to kill off infix entirely, I think that undermined one of the very reasons for Rebol's existence....which is to open up homoiconicity and an "evaluator-based" language to people who don't want their programs to come out looking like kernel lisp:

($define! $and?
    ($vau x e
        ($cond ((null? x)         #t)
               ((null? (cdr x))   (eval (car x) e))
               ((eval (car x) e)  (apply (wrap $and?) (cdr x) e))
               (#t                #f))))

A certain type of person sees that and wants to run screaming. And despite being able to understand it perfectly well, I feel like having a language of equal power where I can write if x = y + 2 [...] feels better, even if I have to learn the ropes a bit to realize why I can't write if y + 2 = x [...] and expect that to work.

So if we find the dial of naturalness wasn't tuned exactly optimally in historical Rebol, I think we should challenge it, because that's what Ren-C has always been about. And I don't want us to lose the ability to "re-skin" the system so it can run a module with Rebol2 semantics (or where I imagine most future Rebol users to come from, a Red skin).

I need to go back and find the point of explanation when I talked myself into why there needs to be tight and non-tight arguments. I'm pretty sure we pinned down it was a requirement, that we couldn't have a uniform policy on how left hand sides were evaluated. :frowning:

But I just got to wondering if we'd be better off if logic operators like OR were left-hand non-tight.

if any [not deep url? path] [
    create path
    return path
]

That would be more readable as:

 if not deep or url? path [
     create path
     return path
 ]

Though it looks nice, the non-short-circuit nature of these operations is going to be counterintuitive. As it happens, by the way, || and && are not short-circuit in C++ if they are overloaded.

As the answerer there says, there's ways other languages can address it. But there's not a lot we can do about it. You can't short circuit an expression in an evaluative slot in a generic way. :frowning:

But just because we can't generically short circuit an expression doesn't mean there might not be a trick to doing it for some cases, and then error if it's not one of those cases. e.g. OR could be a sort of enfixed variadic, and there could be an operation which skips a varargs and will fail if the skipped argument (or any of its arguments, recursively) are themselves variadic. :-/

I'm going to put that on a shelf to think about before my brain melts. One thing it would need to work would be a sort of limited form of DO which ran but wouldn't run variadics, because you don't want to wait until you have an instance that uses the skipping mechanic before finding out your code was "malformed".