Speaking With Tics: Should Quoted Arguments Always Be Apostrophe'd?

In the past it has been discussed whether it is better or worse to have quoted things be apostrophe'd.

Historical Rebol actively prohibits the use of a lit-word! with FOREACH for the loop variable:

>> foreach 'x [1 2 3] [print [x]]
** Script Error: foreach expected word argument of type: get-word word block

R3-Alpha and Red don't allow it either.

But it might seem you'd want to be able to do this in Ren-C...because there are quoting constructs that can quote backwards:

>> backquote: enfixed func [:x] [print ["I backquoted" x]]
== make action! [[:x] [...]]

>> foo: backquote
I backquoted foo:

So if you try for-each backquote, the backwards-quoting BACKQUOTE would get the FOR-EACH before the forwards-quoting FOR-EACH could get the BACKQUOTE. That's just the order that the evaluator works in (unless there's nothing to the right of BACKQUOTE, which is the exception that would allow HELP BACKQUOTE to work).

You can work around that with:

for-each ('backquote) [1 2 3] [print [backquote]]

But it seems like it would be nicer if you could write it without the parentheses.

Should All Quoting Sites REQUIRE Quoted Input?

The quoted parameter convention exists to save you the trouble of quoting the callsites. If this isn't what that's for, then what is it for?

I'd be happy to buck the status quo if I thought it were clearly wrong. But when we look across the board we see how nice and fluent type of foo looks vs. 'type of foo. Over time it gets internalized to the point that type: type of foo doesn't feel weird at all (thought it might look so when you see it the first time).

While there's something to be said for the educational value of seeing when a word is being used by name vs. by value, it's just... "uglier"

type: 'type of foo

We seem lose something about knowing the "rules of OF" and the parts of speech involved by context, in order to add a bit of visual noise. To my tastes, it seems this is not in the same "clear win" zone over type-of and type? that we were in before.

It seems to me a core experimental theory in the language is that there is value in allowing acclimations in our mind to writing things that "start looking natural" even if they don't follow systemic rules. That speedbump of typing type of x of hitting the apostrophe key, as well as the visual jarringness, suggests it's a place to take advantage of not quoting when it's possible.

Should All Quoting Sites Merely PERMIT Quoted Input?

I mentioned that the current workaround for slipping past cases of things like backquoting operators is to use a GROUP!.

for-each ('foo) [1 2 3] [print [x]]

We might ask if the operation with the quoting slot should be lenient and let you just alternately write:

for-each 'foo [1 2 3] [print [x]]

Quoted words won't dispatch functions, so you don't have to worry about FOO's associated behavior if you do this.

A problem with being lenient in that way is that now the function spec is expanded to where it has to accept either WORD! -or- QUOTED! in its quoted slot. That complicates things, and now the author of the quoting function needs to be bothered with the quote-or-not of their argument.

If you take the easy-way-out and just DEQUOTE your argument (which leaves it as is if it's not quoted), then you may be permitting things with arbitrary numbers of quotes.

for-each '''''''''foo [1 2 3] [print [x]]

I'm pretty sure UNQUOTE should require its argument to be quoted, for the sake of sanity. So the implementation of functions like FOR-EACH would get hairier if they were to have to check and preprocess their arguments, e.g. to permit one-and-only one level of quote that they strip off. This could be made easier with some helper for the purpose, though.

A Benefit To Requiring Quotes

I've mentioned in the past that for things like FOR-EACH, if we required the first argument to be quoted then there could be a special interpretation for when there wasn't a quote. e.g. just run a function without naming a variable:

>> for-each ["No" "Variable"] :print
No
Variable

This keeps you from having to write something like:

for-each _ ["No" "Variable"] :print

A plain function declaration isn't particularly interesting in this form, even with a lambda it's wordier (and symboly-er) than the old-style without:

for-each [1 2 3] x -> [print [x]]

But when you bring in something like "POINTFREE" it gets more interesting:

>> block: [a b c]

>> for-each [1 2 3] (<- append block)

>> block
== [a b c 1 2 3]

With the idea that # is legal, it might look better if you "opt-in" to the variables:

for-each # [1 2 3] (<- append block)

This would let you do a template for what you want and don't want passed to your function.

>> block: [a b c]

>> for-each [# _] [1 2 3 4] (<- append block)

>> block
== [a b c 1 3]

This allows us to justify why the variable slot has usefulness even if we're not using a variable. Maybe that's the answer. And if we shorten FOR-EACH to simply EACH it would come out cleaner, even in complex invocations:

each [# _] [1 2 3 4] (<- append block)

Switch to # for opt-in on variables, allow (but don't require) QUOTED! ?

...is this a plan? Does it cover all the angles?

Can't wait to see the feedback on this one. :innocent:
I think of a natural word! as a get-word; if I want to pass an inert word! to be used in a for-each body, it seems more consistent that it be a quoted! (note: quoted! not necessary when passing a block! of words). I have no problem with it being required either, but I imagine this is going to be a problem for some developers and I would understand their resistance to the idea.
I'm still trying to wrap my head around the # opt-in.

A new twist has come along that we are looking at a world where quoted things are not bound in the evaluator.

So if we decided that FOR-EACH took its argument normally, then for-each 'x [1 2 3 4] [...] would not be passing a binding for X to FOR-EACH.

That doesn't sound bad. In fact it sounds good: FOR-EACH isn't reading the variable, it's giving the variable a meaning inside the block.

You might argue that sounds so good that you should use quoting when passing a block in with multiple variable names, just to avoid getting stray bindings: for-each '[x y] [1 2 3 4] [...]

But wait... FOR-EACH has another feature of letting you evaluate expressions to get the names:

for-each [(first [x z]) (second [z y])] [1 2 3 4] [...]

If you quote the block, FOR-EACH won't receive binding on it, and can't evaluate those groups.

This brings us to a related thought: what if a parameter that is quoted by convention in the function arguments doesn't get binding information... as it wouldn't if the material had been quoted at the callsite?

That would suggest that if you are allowed to write for-each x then the quoting convention in effect wouldn't give you the bindings in the case of for-each [...], as both situations went through a quoted parameter convention.

You may be able to still handle for-each :var-name or for-each :(var-name) as a means of indirection, so long as you defined the parameter to use "soft quoting". That mechanism in the evaluator decides to not quote in those escaped cases, and does an evaluation. But the soft quoting of today does not "evaluate" blocks as one of the exceptions, which would be required to bind them in the new model.

In a world where quoted parameters don't get the evaluator's influence from a binding step, this would seem to point to a rule like "dialected arguments shouldn't be quoted". Which would push the idea that the tic on FOR-EACH should be at the callsite, so the evaluator is in effect to facilitate the interpretation needed for the block case.

But Let's Not Be Too Hasty to Abandon Pleasing Syntax

While I like the idea of there being a natural flow to decisions in a model for binding that "works", the flip side here is that I don't like losing the ability to make alternative choices.

Case in point: I have liked being able to use GROUP! instead of BLOCK! in the syntax of AND and OR today. e.g. the following AND will short circuit and not run the second group if FOO-DEFINED is falsey:

if foo-defined and (foo > 10) [
    ...
]

It also permits you to use words in that second slot, so long as they don't look up to a function:

if foo and bar [
    ...
]

This hinges on breaking the traditional rules of the evaluator by quoting the second argument. That gives you the ability to short circuit the group. But if you couldn't get binding information on quoted parameters, it would force the syntax:

if foo-defined and [foo > 10] [
    ...
]

Also, the concern that BAR might be a function with side-effects would mean you couldn't protect against that, so the block would be needed in that case as well:

if foo and [bar] [
    ...
]

Losing the ability to write the source you want for the sake of homogeneity doesn't sound good to me. Hence I think that calls into question whether it should mean losing things like for-each x or type of y just "because...rules".

But what it may mean is that these constructs have to acknowledge that they're being a bit weirder when gathering their parameters. Maybe AND and OR decide that being in this extra-weird class is worth it, and FOR-EACH decides it's not... I don't know.

Actually... today the syntax for-each 'x differs from for-each x in that the former means to not create a new binding, but to do the assignments to an already-existing X variable.

I'm not sure if that's the greatest idea (and I frequently forget the feature exists), but it can be argued as useful.

It applies to if you're using the block syntax as well, e.g. for-each ['x y] would use an existing X variable and create a new Y variable and bind it into the code block.

Another good example to talk about is LET. When we write:

foo: func [x]
    let x: 1 + x
    print ["new x is" x]
]

>> foo 2
new x is 3

We don't want LET to receive the evaluated product of 3 (which has already been assigned to X). We want it to receive the x: as a literal value... then we want it to be able to take one evaluative step to the right using the old bindings, and assign that to a new binding for X. Then we want that new binding injected into the flow of evaluation for the next statement.

This would be much less satisfying as:

let 'x: 1 + x

You could seemingly accomplish the same number of characters typed to drop the colon:

let 'x 1 + x

But we use the colon and non colon versions to distinguish whether an assignment is intended in the same step as the LET or just create the variable:

let x: 1 + x

let y
while [y: try take block] [probe y]

Or, Use A Block?

Block fans might say LET should be a dialect and use a block:

let [x: 1 + x]

That would get you the evaluation suppression you want, and let 'x could be a shorthand when you didn't need an assignment.

It has some advantage for multiple lets without repeating the LET.

let [
   x: some stuff
   y: some other stuff
   z: still more stuff
]

vs.

let x: some stuff
let y: some other stuff
let z: still more stuff

(A combined LET has the potential to be a little more efficient, doing its creations in single object.)

Blocks are contentious with multi-returns, today you can write:

let [a b]  ; variables with no assignment
let [x y]: your multi-return-generating code here

With a block, you'd have to do multi-returns as:

let [[x y]: your multi-return-generating code here]

And let [a b] would presumably just be an error, or the dialect would be somewhat inconsistent.

Altogether, I like it how it is. While arguments can be made either way, I think the current syntax fits into the idea of dressing the language up so it looks "normal", despite running on very different mechanics.

But multi-let seems useful. Maybe it could use a different special pattern:

let [[
    x: some stuff
    y: some other stuff
    z: still more stuff
]]

Or be less fun, and just use a refinement

let/multi [
    x: some stuff
    y: some other stuff
    z: still more stuff
]
1 Like

‘Normal’ is a very subjective word to use. From a Haskell perspective, the version with the blocks strikes me as much more ‘normal’, and I do tend to strongly prefer it.

But also… I think you underestimate how odd Rebol looks already, coming from most other programming language. It took me a while to figure out how to mentally parse code: ‘where’s all the parentheses? where are all the names coming from?’ Even the choice to delimit blocks with square brackets is extremely unusual amongst programming languages.

Now that $ means "produce bound form under evaluation", this would seem to suggest that the $x form would be the consistent cue for "use existing binding".

>> foo: lambda [x] [
       print [x]
       for-each $x [1 2 3] [print [x]]
       print [x]
    ]

>> foo 10
10
1
2
3
3

Does that mean we should switch to the idea of an evaluative argument for the parameter, where you pass 'x or $x ?

Well...hold on a second. In both cases, the FOR-EACH would be receiving just the word X. But in one case it's bound, and in the other it is not.

Are we confident enough in bindings being under control that the "invisible" property of whether something is already bound or not can act as the cue for whether to create a new binding? (See related concern about MAKE OBJECT! having a SET-WORD! with a binding at the top level). I'm skeptical.

Also, consider when we want this:

x: 10
y: 20
for-each [x $y] [1 2 3 4] [
   print [x y]  ; 1 2 and then 3 4
]
print [x]  ; 10 (undisturbed due to new binding)
print [y]  ; 4 (reused variable)

Remember that both X and $Y are unbound inside the block, but the block's environment has available bindings for X and Y going in. Then the $Y signals in the "for-each variable dialect" to reuse the binding, while the plain X suggests not. It wouldn't matter if we did this with ['x $y], which I think is less desirable... the point is just that we wouldn't be doing this with [x y] and forcing people to control it with unbindedness:

 x: 10
 y: 20
 for-each compose [x (in [] 'y)] [...]

This suggests in the non-block case that it would be more conservative to take the parameter as an $arg which would quote-but-bind, and then go off the cue of whether the $ was present or not.

And what about the potential contradiction, where you have a plain word and it's not unbound?

 x: 10
 y: 20
 for-each compose [(in [] 'x) $y] [...]

Here the "dialect notation rule" says it's a plain word, hence you want a new binding. But it has a binding. Should that be an error to help point out the contention, or should it just assume the binding is superfluous?

Moreover... should a binding ever be treated as superfluous? This is a major question for consideration in the "mostly-unbound world".

Ripple Effect: Meta-Passthru LET Parameters

I'd just been thinking that the new FENCE! proposal would be able to solve something we couldn't really do before, which was mark a multi-return as being both ^META -and- the main overall result.

 >> var: @z

 >> [x {^y} (var)]: pack [1 2 3]
 == '2

 >> x
 == 1

 >> y
 == '2

 >> z
 == 3

This wasn't achievable when marking the "circled" result was done with the @ symbol. Using the {FENCE!} to mark the circled result (defaults to first result) can accomplish it.

But if LET moves away from using quoted material to mean "don't create a new binding" and uses $, then there's a new contention: how to reuse an existing binding and make it meta.

>> (let [^x ...]: 1, x)
== '1

>> x: <before>

>> (let [$x ...]: 1, x)
== 1

>> x
== 1

>> let [???x??? ...]: 1
== '1

>> x
== '1

That shows an advantage of using the quote mark to say "have LET ignore this part", because it can mean ignore any part. So let ['^x]: ... could achieve the intended purpose, telling the LET "this section isn't for you to worry about" and having it go through a step where it produces [^x]: ... with the binding intact... falling through to the multi-return logic with whatever binding was in place (or whatever left-quoting construct wanted the SET-BLOCK).

So maybe LET just doesn't follow the same rule as FOR-EACH? Or maybe FOR-EACH should not be changed, and its unusual behavior of "using the binding of quoted things, but not of non-quoted things" should be left as-is?

x: 10
y: 20
for-each [x 'y] [1 2 3 4] [
   print [x y]  ; 1 2 and then 3 4
]
print [x]  ; 10 (undisturbed due to new binding)
print [y]  ; 4 (reused variable)

But this really feels backwards in a "quotes mean you don't get a binding" rule for the main evaluator.

It doesn't come up all that often. Maybe the following?...

>> let [$($ '^x) ...]: 1
== '1

>> x
== '1

"Give me a literal ^X that's bound. Then have the multi-return be guided by the meta that's on the word."

But that doesn't correspond to any of how SET or GET work... they're agnostic about the word type.

>> set ($ '^x) 1
== 1

>> x
== 1  ; not '1 (and I don't think it should be)

We could say that you can put the $ designator spaced off, and it's assumed to apply to the next thing:

>> let [$ ^x ...]: 1
== '1

This might not be a problem... although the LET still has to preprocess the bindings in the block. It's getting more invasive when it has to look into the details vs just say "oh, it's quoted, unquote it but ignore it otherwise."

Lots to think about here.

  • I do think the FOR-EACH etc. should be using the $var notation to mean "use existing binding".

    • I doubt that it should be switched to for-each 'x and for-each ['x] for make new binding, but should stick with for-each x and for-each [x]

      • I definitely don't think LET should be switching to let 'x: and let ['x]: for make new binding
  • Fences are going to probably be the right pick for "circling" which multi-return result to give back

  • If the $ operator is allowed to stand alone in the multi-return dialect, that probably gives full coverage of the need to opt out of LET creating a binding for some portion of a multi-return.

    • This makes the work LET does on the "dialect" it's processing inside a SET-BLOCK! more invasive than its former "if quoted, drop quote level and ignore" rule

    • It may be that LET's rules are just different from FOR-EACH's rules

Having read through this a couple of times, I think this makes sense to me… simply because LET has extra responsibilities that FOR-EACH doesn’t, namely (a) making stuff meta, and (b) setting the overall result.

I feel a big cause of the difficulties here is a lack of compositionality. Really, we’d like to have something like $^x for ‘reuse existing binding and make it meta’, but no such type exists (at least in current Ren-C). Perhaps one way to solve this might be to abandon specialised types altogether, and just put all modifiers spaced off, perhaps in a fence too:

>> [_ {$ ^ x}]: pack [1 2]

But of course there’s also the underlying issue:

I don’t (yet) have any good answer for this, but I definitely think it’s the right question.

1 Like