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, @hiiamboris 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 antiforms (antiform of FRAME!). And FUNC or DOES create antiform actions.

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

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

You can put quasi-FRAME!s, plain FRAME!s, and quoted FRAME! in blocks. Just not antiform 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."]] 
== [~&[frame! []]~]

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

>> otherblock: copy []
== []

>> temp: block.1
== ~&[frame! []]~

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

I used REIFY there and got a quasi-frame. 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."]] 
== [~&[frame! []]~]

>> run block.1
Evaluated.

>> block: reduce [lift:lite does [print "Evaluated."]] 
== [&[frame! []]]

>> apply block.1 []
Evaluated.

(See post on difference between REIFY and LIFT, 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 FRAME! into an FRAME! antiform...

 >> active-var: runs block.1
 == \~&[frame! {active-var} []]~\  ; antiform  <-- also cached name, neat!

 >> active-var
 Evaluated.

:clap:

You can use UNLIFT more generically to get an antiform back from any quasiform (not just actions), or ANTI to get an antiform 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 antiform 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 antiform.

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

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 give a lifted value
3 Likes