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 true [
    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.