Picking ACTION!s from BLOCK!s In The Age of Isotopes

In Red's Design Questions wiki, they bring up the issue of why picking functions out of blocks runs them:

red>> block: reduce [does [print "Evaluated."]]
  == [func [][print "Evaluated."]]

red>> equal? block/1 first block
Evaluated.  ; d'oh
== false

In the Gitter conversation, Boris calls it a "shady area" that they can't get out of "under the current semantic model". He suggests it is clear that block/1 should not run an action, but possible that block/word would mean to.

They contemplate the idea that block/1 would not run the action, but pick it:

red>> block: reduce [does [print "Evaluated."]]
  == [func [][print "Evaluated."]]

red>> equal? block/1 first block  ; hypothetical behavior
== false

I will argue this doesn't really help you much, the problem just becomes one step removed. Quite often people are picking things out of blocks to put them in variables, and you'll just get bit there instead:

red>> otherblock: copy []
== []

red>> temp: block/1
== func [][print "Evaluated."]

red>> append otherblock temp
Evaluated.  ; d'oh
== [unset]  ; double d'oh

Isotopes Bring The Better Semantic Model!

In Ren-C's concept (as I'm working on it), the only actions that will run from a word reference are those that are isotopic actions. And FUNC or DOES creates an isotopic action.

So you would be stopped from making an illegal block up front:

>> block: reduce [does [print "Evaluated."]]
** Script Error: Invalid use of ~#[action! []]~ isotope

You can put quasi-ACTION!s, plain ACTION!s, and quoted actions in blocks. Just not isotopic ones.

Whichever you choose, the equality test will work... and picking out a value into a variable will give you an inert variable to work with, that can be used with things like append!

>> block: reduce [reify does [print "Evaluated."]] 
== [~#[action! []]~]

>> equal? block.1 first block
== ~true~  ; isotope  <-- didn't run and print "Evaluated", yay!

>> otherblock: copy []
== []

red>> temp: block.1
== ~#[action! []]~

red>> append otherblock temp
== [~#[action! []]~]   ; <-- didn't run and gave sensible block out

I used REIFY there and got a quasi-action. BUT which would you rather put in the block: a quasi-action or a plain one? This depends on what you plan to do with the block. A plain action will execute when encountered by the evaluator, while a quasi-action will evaluate to an isotopic action--suitable for assigning via SET-WORD! when you meant to make that word dispatch the function when referenced.

My leaning is to say that either form can be used with APPLY, RUN, or the terminal path form:

>> block: reduce [reify does [print "Evaluated."]] 
== [~#[action! []]~]

>> run block.1
Evaluated.

>> block: reduce [concretize does [print "Evaluated."]] 
== [#[action! []]]

>> apply block.1 []
Evaluated.

(See post on difference between REIFY and CONCRETIZE, and the search for a better term...)

If you want to pick an action out of a block and put it into a variable, where it will execute from that variable, there is the RUNS transformer. It will turn a quasi or plain ACTION! into an action isotope:

 >> active-var: runs block.1
 == ~#[action! {active-var} []]~  ; isotope  <-- note: also cached name, neat!

 >> active-var
 Evaluated.

:clap:

You can use UNMETA more generically to get an isotope back from any quasi-form (not just actions), or ISOTOPIC to get an isotope from a plain form.

The Invariant Is What Counts, Here!

The mountain that has been climbed is that we can now say that for any block, this is true:

block2: collect [
    for-each item block1 [keep item]
]

assert [equal? block1 block2]
  • I've just covered that there are no isotopic actions to implicitly execute; you'd get an error trying to put them in the block.

  • There are no blocks/groups/paths that will splice into the target, because splicing requires an explicit conversion to an isotope.

  • There are no "unsets" to trip on that you can find in a block, because the state conveying "unsetness" (none) is an isotope.

Of course with objects, it's going to be a different story. I think we'll still want some safeguards:

for-each [key value] object [...]  ; will error when value is action

for-each [key :value] object [...]  ; will allow action isotopes as-is

for-each [key ^value] object [...]  ; will give a meta value

A key problem here is I'm wondering how much to cross purposes of GET-WORD! between action disarming and the other isotopic states. But, it just takes time to work through.

3 Likes