FENCE!'s Higher Calling: WRAP

Since FENCE! came out I've been reluctant to use it directly in source as MAKE OBJECT!... wondering if there was some higher calling.

I suggested it might be better to have it be the CONSTRUCT dialect, for instance. And that seemed smarter:

>> {x y (1020) ~z~}
== &[object! [
    x: ~null~
    y: 1020
    z: ~
]]  ; ... maybe this is useful?  :-/

But Then... I Looked at WRAP in Branches...

When I looked at that I thought "well, branches take their argument literally, WHY NOT USE FENCE! and have it mean WRAP?"

>> if 1 = 1 {x: 10 print [x]} else {x: 20 print [x]}
10

>> if 1 = 2 {x: 10 print [x]} else {x: 20 print [x]}
20

EUREKA

:cloud_with_lightning:

With these seen, I realized it wasn't just branches. The best... most cross-cutting application of the new FENCE! type under non-dialected evaluation... would be for wrapping code in contexts... and returning the wrapped BLOCK!:

>> {x: 10 print [x]}
== [x: 10 print [x]]  ; bound (to a context with X in it!)

(Note that WRAP in Redbol lore has sometimes been evaluating and sometimes not...but the more useful form here is "make the block bound into the object, but don't run it, just return it.")

I don't think it will become the default code block choice. Because it gathers all the top-level SET-WORD!s and you have to be more careful, to use $foo: for any assignments you want to refer to outer-"scoped" things:

x: 10
y: 20
assert [1324 = all {
    x: 1020

    [$y]: 304   ; [$y]: today means "ignore Y as field of WRAP'd context" 
    set $y 304  ; alternative trick (more like historical trick)
    (y: 304)    ; yet another historical trick to dodge wrapping Y

    x + y
}]
assert [x = 10, y = 304]

So don't expect to see people reaching for this every time they make a code block. Brackets will remain king, especially since LET gives you a nice-enough tool most of the time if you need to create a new variable in mid-block.

But this makes it more terse if you are doing an evaluation where you're creating more variables than you are assigning outside the scope.

FENCE Function Body: "Make Top-Level Assigns Locals"

foo: func [x y] {
   local: 10
   if x [
      not-local: 20
      ...
   ]
   ...
}

Mechanically speaking, this requires FUNC body to be taken literally. But it can build the locals into the FRAME!, and use its knowledge of what's already in the frame so that it doesn't create locals for arguments...

We see that now you have the power you always needed... to make a choice of scope establishment at each level, since it makes little sense to make it deeply when you don't know what dialect might be in effect:

foo: func [x y] {
   local: 10
   if x {
      new-variable: 20  ; not a FRAME! local, but new to this scope
      x: 30  ; would be creating a new variable (vs. $x:)
      ...
   }
   y: 40  ; wouldn't create new variable? (FUNC got FENCE!, can filter)
   ...
}

There's a bit of nuance here with a difference between the logic of the outermost scope vs. the inner ones, but I think that nuance is learnable. (Don't like it? Don't use FENCE!, use BLOCK! and LET... and be a little wordier and less efficient. This just helps the efficiency-hounds have a pithier way of saying what they want.)

It's a really high-leverage choice, and I think it stands out visually well enough... especially when you are in the rhythm of expecting to see BLOCK!, making the choice all the more powerful.

{{...}} Probably Should Be Boring :frowning:

It's a little risky to carve something out of a generic evaluative tool to give {{...}} as some special meaning (CONSTRUCT, etc). Generative code might get bitten. It thinks it's inserting scoped code somewhere, which is also scoped, and you get a wacky composite behavior.

(Though ^{...} could be argued as a pretty cool way of asking for the OBJECT! that was generated by the WRAP, vs. the evaluative product. Will have to think about that.)

But there's a plus to having {{...}} and ((...)) being largely meaningless in the evaluator. Since you wouldn't have a good reason to write them as they do the same thing as {...} and (...) generally, they make good callouts for COMPOSE or similar to do templating/etc.

I think CONSTRUCT might just be CONSTRUCT, and then in some contexts you might use RebindableSyntax to say "the {...} or {{...}} I use here are constructs" (for instance, that's what it is in locals in function specs, probably other places...)

Sky's the limit here.

1 Like

There's a little bit of an issue with using WRAP on things like loop bodies that are taken by value, because they may or may not run.

 data: []

 for-each 'item data {
     var: ...
     ...
 }

If the behavior of FENCE! is to wrap and create an object, and bind the list to it and give it back as a BLOCK!, you'd be creating an object even when you weren't going to need it... because there'd be no iteration.

Branching code handles this by using literal arguments.

It seems to me that for that reason, loops should follow branch rules, and take their bodies as literals... so that you only pay if the loop runs at least once.

It's good for FENCE!, and it's good for synthesized bodies via GROUP!:

 data: []

 code: '[item mold print]

 for-each 'item data (
     print "don't want to run this when data is empty"
     reverse code  ; would turn code to [print mold item]
 )

...and for that matter, it's arguably good even for plain old BLOCK! (because you're not gluing a binding into a block evaluating it, leaving that reference for the GC to clean up).

Anyway...loop bodies are basically branches, taken repeatedly.

So this is probably where things were going, anyway.

So I implemented this, and it's pretty amazing... I only had one little thought...

It's A Little Hard To See

One might argue that lots of languages have different behaviors for braces and brackets, and we're expected to see the difference (or if we have a hard time seeing the difference, change our fonts or syntax coloring to help us discern).

But what's a bit weird here is that the braces are sitting in slots that are contextually not special; your eye can miss it.

For instance, here's some code with braces:

valid: ["~abc~" "~a|b~"]
for-each 'str valid {
    word: parse str [/to-word between '~ '~]
    bad: transcode:one str
    assert [quasi? bad]
    assert [word = unquasi bad]
}

Were it brackets:

valid: ["~abc~" "~a|b~"]
for-each 'str valid [
    word: parse str [/to-word between '~ '~]
    bad: transcode:one str
    assert [quasi? bad]
    assert [word = unquasi bad]
]

How natural is it for your brain to "see" that these are different? Or is it easy to overlook?

The same could be said perhaps of parentheses:

valid: ["~abc~" "~a|b~"]
for-each 'str valid (
    word: parse str [/to-word between '~ '~]
    bad: transcode:one str
    assert [quasi? bad]
    assert [word = unquasi bad]
)

But for some reason I feel the parentheses stand out more. (It does depend on the font.)

One could perhaps argue that this means the WRAP construct should be {{...}} instead:

valid: ["~abc~" "~a|b~"]
for-each 'str valid {{
    word: parse str [/to-word between '~ '~]
    bad: transcode:one str
    assert [quasi? bad]
    assert [word = unquasi bad]
}}

Then you could say "well, that frees up {...} for other things, like CONSTRUCT"

But I'd rather say this is left as a stylistic choice. Since {{...}} would by default mean the same thing as {...}, you could decide on a case-by-case basis if you wanted to call attention to "this here is a FENCE!"

Letting people decide for themselves is on brand. The same person might have different feelings for when things are on their own lines vs. inline:

>> x: (), if 1 = 1 [x: 10 print [x]] else [x: 20 print [x]]
10

>> if 1 = 2 {x: 10 print [x]} else {x: 20 print [x]}
20

It's harder to overlook the distinction. Forcing people to throw in an extra brace feels wrong:

>> if 1 = 2 {{x: 10 print [x]}} else {{x: 20 print [x]}}
20

Guess I'm saying "double up if you want". We can make it cheap in the evaluator (e.g. if you notice a singular FENCE! is running inside of a FENCE!, don't do anything weird like make a dummy empty object for the outer one, just zap forward and do what the inner fence would do).

Note: the case of FUNC bodies deciding to merge with locals if you use braces for the body defeats the idea of {...} and {{...}} being the same... it would have to explicitly dig to treat them as such.

This makes me a little more uncomfortable with the idea of braces acting specially at that top level than I already was.

Probably-uselessly, there's also the default interpretation, which is... top-level statics.

foo: func [x] {
   y: default [0]
   y: y + x
   return y
}

>> foo 10
== 10

>> foo 20
== 30

This is based on the idea that you passed FUNC a bound block that has a definition for Y. But every time you enter the function it takes it from the top, so you're overwriting the static every time (hence the need for DEFAULT to avoid that).

That's so much more useless than being a top-level gathering intent that I just can't tolerate the missed opportunity.

But treating the top-level {{{x: 10}}} as if you'd written {x: 10} feels like a bug. In the case of FUNC, there is meaning to the top level, and meaning to not being at the top level.

This means no doubling up of braces for visual standing-out (akin to how you can't "just double up brackets" to stand out).

I'm not actually that uneasy with people having to set their font or syntax highlighting to see braces vs. brackets better, if they think there's a problem. I just do a lot of devil's-advocacy.

It also occurs to me... since the loop is taking the branch literally, it could scan it and omit loop variables from the object.

valid: ["~abc~" "~a|b~"]
for-each 'str valid {
    str: reverse str  ; WRAP won't consider STR for the object
    word: parse str [/to-word between '~ '~]
    bad: transcode:one str
    assert [quasi? bad]
    assert [word = unquasi bad]
}

Here there really would be a difference with two braces:

valid: ["~abc~" "~a|b~"]
for-each 'str valid {{
    str: reverse str  ; WRAP *would* consider STR for object (PANIC!)
    word: parse str [/to-word between '~ '~]
    bad: transcode:one str
    assert [quasi? bad]
    assert [word = unquasi bad]
}}

This is bonkers. But pretty amazing.

:face_with_spiral_eyes: