There is a foundational truth about code composition:
The system cannot be psychic and know if you want meaning to come from the place
where you are building the code -or- from the context in which the code will be eventually interpreted.
Ren-C has the power to talk about both
-
If you leave the code unbound (
'quoted) then it will gather the meaning from where you wind up evaluating it. -
If you BIND the code then it picks up meaning in the context of composition, and the later usage won't override it. (Note: BIND is an arity-1 operation in Ren-C, and other operations are used to bind to something other than the "current" evaluation context.)
No psychic evaluator required.
>> make-unbound: func [x] [return '(x)]
>> make-bound: func [x] [return bind '(x)]
>> x: 1020
>> make-unbound 304
== (x) ; unbound
>> make-bound 304
== (x) ; bound (into make-bound func instance's context)
>> '[1 +]
== [1 +] ; unbound
>> bind '[1 +]
== [1 +] ; bound (into console context where x: 1020 lives)
>> [1 +]
== [1 +] ; bound (`[...]` and `bind '[...]` both act the same, by design)
>> eval append [1 +] make-unbound 304
== 1021
>> eval append [1 +] make-bound 304
== 305
Binding Has A Shorthand: $
Because control over binding is so fundamental (and needed so frequently), there's a special "binding quote" decorator, called a "TIE". This gives you a briefer way to bind things:
>> bind 'x
== x ; bound
>> $x
== x ; bound
All bindable types--$WORD, $TU.P.LE, $CH:A:IN, $PA/TH, $[BL O CK], $(GR O UP), ${FE N CE}--have a TIED! form.
TIED! values aren't just a good shorthand for binding quoted material in the evaluator, but also crucial dialecting parts.
In addition, a standalone $ is just a synonym for BIND.
>> $ 'x
== x ; bound
You can make a TIED! value from a plain one with TIE, and remove the decoration with UNTIE.
>> item: tie '[a b c]
== $[a b c] ; unbound
>> untie item
== [a b c] ; unbound
(More general mechanisms for decoration exist as well.)
Unbound Code Becomes Useful Currency
This is working Ren-C code, that shows off doing something that people used to wish they could do (but couldn't!):
>> doubler: func [x] [
let code: copy [add x]
append code to word! "x"
print ["Doubled:" eval code]
return none
]
>> doubler 10
Doubled: 20
Note that from the evaluator's point of view, ADD and X inside the code block aren't bound... so the X you added being unbound made it no different.
In fact, none of the code in the function body is bound! The binding trickled down as the evaluator descended... and when the unbound block [add x] was evaluated, it synthesized a bound BLOCK!, which wound up in the CODE local variable...
But on each successive function call this process will happen again--meaning (crucially) that the [add x] that lives inside the function body never gets bound.
This stands in contrast to what would happen if the function body was built by something like a COMPOSE, where [add x] had been bound. In which case: that binding would not be overridden by the "trickle-down" of the evaluator... and the block would be interpreted with the meaning the person who composed it in intended.
Let's see that in action:
>> x: 1000
>> doubler: func [x] compose [
let code: copy ($[add x])
append code to word! "x"
print ["Doubled:" eval code]
return none
]
>> doubler 10
Doubled: 2000
Works as written (although it didn't end up making use of the X that was passed!). So let's do a little twist with a laser-focused COMPOSE with just the X bound outside:
>> x: 1000
>> add1000: func [x] compose:deep [
let code: copy [add ($x)]
append code to word! "x"
print ["Added a Thousand:" eval code]
return none
]
>> add1000 20
Added a Thousand: 1020
String Interpolation Becomes Possible
Once you can connect unbound words with values while evaluating, you have the necessary ingredient to accomplish string interpolation:
>> num: 1000
>> compose "Hello there (num + 20), World!"
== "Hello there 1020, World!"
Addresses Key Flaws in the Rebol2/Red Binding Model
Historical Redbol's binding was done in a wave at LOAD-time, deeply on the entire code...providing the baseline for library functions (PRINT, etc.) or global variables. But doing this a-priori meant stray bindings were applied everywhere, and had to be overridden.
As a simple example, locals in functions:
Rebol []
foo: 10 ; global foo
bar: func [foo] [
return foo + 20 ; FUNC receives body with this FOO bound to global foo
]
In this model, things like FUNC would have to override the LOAD-time binding it receives on the FOO in its body. You face questions...
-
What if you have a binding to something called FOO in a body of a function, but the code was generated... and it meant its notion of FOO, not the local?
-
What if important definitions come along later--after the initial "wave" of binding?
There were epicycles of this bad idea in things like locals-gathering FUNCT
The new rule is simply this:
If you only put bindings on when you mean them, you're not in a position of having to decide "is this binding meaningful or not"... if something has a binding, it's meaningful.
It doesn't resolve every tricky binding question that comes up...there are still plenty of hard situations to reason about. But as a basic premise, this has been serving Ren-C very well...cleanly enabling things that were intractable before.