hiiamboris's WITH

I've mentioned that binding might be helped by a dialect, and it turns out there is some precedent in hiiamboris's WITH:

USAGE SUMMARY

with x  [..]          == bind [..] x
with 'x [..]          == bind [..] 'x
with :fun [..]        == bind [..] :fun
with [:fun] [..]      == bind [..] :fun
with [x y 'z] [..]    == bind bind bind [..] 'z y x
with [x: 1 y: 2] [..] == bind [..] context [x: 1 y: 2]

EXAMPLES

omit the path to an object, but work inside it's context:

do with face/parent/pane/1 [
    color: red
    text: mold color
    visible?: yes
]

if true with system/view/fonts [print [serif size]]

f: func [/- /+ /*] [        ;-- redefines important globals locally
    (do something with local flags)
    foreach x [set..] with system/words [
        (do something with global * + -)
    ]
]

create static storage for functions where existing literal forms don't allow you to:

factorial: func [x] with [cache: make hash! [0 1]] [
    any [
        select/skip cache x 2
        put cache x x * factorial x - 1
    ]
]

anonymize words used during initialization of the program:

first item in the block should be of set-word! type

do with [x: 1 y: 2] [
    z: x * y
    ... other code that uses x or y ...
]

bind a block to multiple contexts at once (in the list order):

First item in the block should be of word!/get-word!, path!/get-path! or lit-word! type

  1. words and paths values are fetched, while lit-words are converted into words
    get-words and get-paths should be used for function context, otherwise they get evaluated

  2. if resulting value is a context, block is bound to it
    if resulting value is a word, block is bound to the context of this word

    the following example illustrates usage of words and lit-words:

    a: b: x: y: none
    c: context [
        a: 1
        b: 2
        f: func [x y] [
           ; calls `with` internally
           print composite [self 'x] "a=(a) b=(b) x*y=(x * y)"
           ; equivalent
           print composite [self :f] "a=(a) b=(b) x*y=(x * y)"
        ]
    ]
    

Thus, with [c] is equivalent to with c, while with ['c] - to with 'c.

WHY IS IT DESIGNED LIKE THIS?

  1. It does not evaluate

    with does not evaluate the block, so:

    • it can be used after contexts, ifs, loops, funcs, etc.
    • it can be chained with x with y ...

    I've found that this makes code much more readable than it would be with bind.
    Prefix it with do if you want immediate evaluation.

  2. It accepts blocks

    Design question here was - if we allow block! for ctx, how should we treat it?

    • convert it to a context? ctx: context ctx

      that shortens the with context [locals...] [code] idiom

    • list multiple contexts in a block as a sequence and bind to each one?

      that shortens with this with that [code] idiom

    Personally, I've used the 1st at least a few times, but 2nd - never, though I admit there are use cases.
    This can be solved by checking type of the 1st item in the block is a set-word or not :wink:
    But still ambiguous! When with gets a word! argument it can:

    • get the value of this word, which should be an object!, and bind to this object
    • get the context of this word, and bind to this context

    When inside a context, 2nd option is nice:

    context [
        a: 1
        f: func [x y] [
            with [self x] [x * y * a]
        ]
     ]
    

    ..where the alternative would be:

    context [
        a: 1
        f: func [x y] [
            with context? 'x with self [x * y * a]
        ]
    ]
    

    When outside of it, 1st option is better:

    x: context [x: 10]
    y: context [y: 20]
    do with [x y] [x * y]
    

    ..where the alternative would be:

    x: context [x: 10]
    y: context [y: 20]
    do with in x 'x with in y 'y [x * y]
    

    But this still can be solved: let word!s evaluate to contexts and lit-word!s, same as we have bind code ctx vs bind code 'ctx:

    context [
        a: 1
        f: func [x y] [
            with [self 'x] [x * y * a]
        ]
    ]
    
    x: context [x: 10]
    y: context [y: 20]
    do with [x y] [x * y]
    

So I think it's best to kill off the <static> annotation in function specs, replacing it with something like WITH.

accumulate: function [
    return: [integer!]
    num [integer!]
    <static> state (0)
][
    return state: state + num
]

transitioning to...

accumulate: function [
    return: [integer!]
    num [integer!]
] with {state: 0} [
    return state: state + num
]

It looks a little more awkward (maybe?).

But statics are a fairly rarely-used ability, and leveraging a generic part like WITH is just a better bet.


So Ren-C DO is for running whole scripts or code from any language (e.g. JavaScript and CSS instead of Rebol), requires a header on Rebol scripts, etc. It's not for evaluating blocks. You have to use EVAL (or unabbreviated EVALUATE).

So instead of do with you'd say eval with.

EVAL WITH would thus be similar to (but more powerful than) what has historically been known as USE.

x: 10 y: 20
use [x y] [  ; Redbol USE
    x: 100 y: 200
    print ["Inside the USE X and Y are" x y]  ; 100 200
]
print ["Outside the USE X and Y are" x y]  ; 10 20

There's not a lot of that in Ren-C. Instead it uses LET, which is generally preferable since it doesn't make you indent your code.

In the narrow cases where I would want to do this, I don't feel it would be that terrible to say:

x: 10 y: 20
eval with [x y] [  ; could be (with {x: y: ~})
    x: 100 y: 200
    print ["Inside the EVAL WITH X and Y are" x y]  ; 100 200
]
print ["Outside the EVAL WITH X and Y are" x y]  ; 10 20

Especially because you often could do the assignments right in place, as you can here:

x: 10 y: 20
eval with {x: 100, y: 200} [
    print ["Inside the EVAL WITH X and Y are" x y]  ; 100 200
]
print ["Outside the EVAL WITH X and Y are" x y]  ; 10 20

My concept for retaking the word USE is to import things into the current scope. So like WITH, but not requiring a level of indentation.

obj: {foo: 1000 bar: 2000}

use obj
print ["FOO is" foo "and BAR is" bar]  ; 1000 2000

Among the many uses for such a thing would be bringing GATHER'd declarations inside a PARSE into scope:

if 1 < 2 [
    let filename: "demo.txt"
    use parse filename [gather [
        emit base: between <here> "."
        emit extension: thru <end>
    ]] else [
        fail "Not a file with an extension"
    ]
    print ["The base was" base]  ; demo
    print ["The extension was" extension]  ; txt
]
; base and extension would not be defined here!

Is WITH the Right Word for WITH?

The thing that bothers me is that WITH is a very generic term, that seems to me to be a good refinement.

>> join:with "," tag! ["abc" "def" "ghi"]
== <abc,def,ghi>

This is really a bind-specific operation. And I wonder if perhaps, since it is going in the direction of dialecting, if it should be the BIND dialect itself?

accumulate: function [
    return: [integer!]
    num [integer!]
] bind {state: 0} [
    return state: state + num
]

In this model, BIND's first argument is the binding instructions, and the second argument is the block (or whatever) to bind.

This would kill the idea I had of the "arity-0 BIND", but that would just be a synonym for the $ operator, and since there's a short operator for it the name of it doesn't have to be quite that short or critical.

I think that it's probably best to say that this is what BIND is. People can just learn that the second argument is the thing that gets bound.

So I was wondering in the design of the WITH-like BIND what plain words should do, as opposed to @word.

One concept I had was that with plain words, BIND could serve the role of "old USE" and create new bindings. Then, an @word would mean that you wanted to bind to that particular word.

x: 10
y: 20
eval bind [@x y] [
   x: 100  ; overwrites X
   y: 200  ; would not overwrite as plain Y word made new variable
]

That would be making a distinction between words and @word that lines up with how FOR-EACH and others treat variables. It was chosen over $word due to the ability to pass the variable reference without a block:

iterate @var [...]   ; the @ sticks, but $var makes a plain bound word

But BIND has a competing intent, which is when you want to bind to the thing the word points to.

foo: make object! [x: 10 y: 20]
bar: make object! [z: 30]

eval bind [@foo @bar] [  ; alternative intent of @
   assert [all [x = 10, y = 20]]
   assert [z = 30]
]

This is more like the PARSE meaning of @ which means "dereference this word".

Another way of doing that could be GROUP! evaluations:

eval bind [(foo) (bar)] [
   assert [all [x = 10, y = 20]]
   assert [z = 30]
]

It's worth pointing out that making new variables with BIND can be done easily another way, with FENCE!, so we could say:

x: 10
y: 20
eval bind [x {y: ~}] [  ; instead of [@x y]
   x: 100  ; overwrites X
   y: 200  ; would not overwrite as {Y: ~} made new variable
]

And I think giving a value to a new variable is probably more common than not wanting to, anyway.

That frees up the @foo to mean "what I intend to bind to is what you get by dereferencing this" intent.

So binding a single word means bind using the binding of that word.

block: [print ["The value of x is:" x]]

foo: func [x] [
    eval bind $x block
    eval bind [x] block
]

foo 10  ; would print the message with x as 10

I can't necessarily prove it offhand, but I feel like having bind [@x y] mean something parallel to for-each [@x y] would be beneficial.

That would indicate producing a new variable for Y, and reusing a binding for X.

This makes BIND able to function easily as a "LET that takes a block"

let x
x: 10

bind 'x [x: 10]

Something about pushing on these symmetries seems more important than giving you a way to dereference variables to implicate the objects they point to, that's briefer than a GROUP!.

(Honestly, multiple binds are probably not that common, and in fact you wouldn't get a lot of benefit out of it by saying bind [(obj1) (obj2)] ... as opposed to bind obj1 bind obj2 ...)

The Binding Dialect Likely Needs More Features

This is sort of the tip of the iceberg in "meanings you might have for binding requests". So could be another way of saying you should dereference, as part of a broader idea of TAG!-instructions:

foo: make object! [x: 10 y: 20]
bar: make object! [z: 30]

eval bind [<in> foo <in> bar] [
   assert [all [x = 10, y = 20]]
   assert [z = 30]
]

So really we're talking about the needs of the most common form.

Although...Ignoring The Binding Is Annoying

This has some consequence, for instance this would have the binding ignored and make a new variable:

 eval bind (bind obj 'x) code

While this would use the binding:

 eval bind (bind obj '@x) code

That would be true of FOR-EACH as well.

It does point out that if you want to bind to a single object's field and not all of it, then it would be helpful to have a notation for that.

eval bind [@obj.x] code

This could suggest that maybe undecorated means "evaluate this", such that these would be equivalent:

 bind foo [...]
 bind [foo] [...]

As opposed to the FOR-EACH equivalency, which would be:

 bind 'foo [...]
 bind [foo] [...]

I don't know. :pouting_cat: I guess I'll just have to try things and see what's most useful.

We currently don't have a user exposed datatype for a "Context stack". Someday we will, and there will be a CONTEXT! type. For now, BINDING OF is a very limited operation that only looks up words to tell you what specific place it gets found in... you don't have its entire context.

So today, if you want to grab a context from somewhere and apply it somewhere else, you use an operation called INSIDE. For instance, here it is making it possible to automatically return a function's last evaluative product:

func: adapt lib.func/ [
    body: inside body compose '[
        return (as group! unbind body)
    ]
]

If you could extract a CONTEXT! you would be able to write this as:

func: adapt lib.func/ [
    body: bind (binding of body) compose '[
        return (as group! unbind body)
    ]
]

This would grab the context off the tip of the BODY block, and then apply it to the unbound product of the COMPOSE.

Binding Dialect Concept: ^body to "Use Tip Binding"

We could avoid having a separate INSIDE operator if BIND had a way of saying "use the tip".

I thought a ^META word might be nice in that dialect:

func: adapt lib.func/ [
    body: bind [^body] compose '[
        return (as group! unbind body)
    ]
]

It's something to consider, though a little disappointing that you need a BLOCK! to suppress the ^META evaluation. (We can't really make BIND's first argument literal the way FOR-EACH can in order to accomplish the Literal Arguments as Proxy for Dialecting... BIND wants to evaluate its arguments.)