Quick and Dirty FLOW Example

At one point I proposed something along the lines of:

>> flow [
       [1 2 3]
       reverse _
       map-each x _ [x * 10]
   ]
== [30 20 10]

Seems Neat. How Hard Is It To Do?

Well, let's just be simple-minded to start.

(Note that in Ren-C, REPLACE means "REPLACE:ALL" by default...)

flow: func [
    block [block!]
    :placeholder [element?]
    <local> flow-result
][
    placeholder: default [_]
    block: copy block
    replace block placeholder $flow-result
    while [not tail? block] [
         insert block $flow-result:
         [block flow-result]: evaluate:step block
    ]
    return flow-result
]

And On This Example, It Works!

Some notes:

  • element? means any non-antiform

  • $flow-result evaluates to the plain word of flow-result, but carrying a binding from the current context (in this case, so that the code in the user supplied block can see the local variable in FLOW's frame)

But How Does It Work?

It simply creates a variable FLOW-RESULT, and then splices it into a copy of the block:

flow [
    [1 2 3]
    reverse _
    map-each x _ [x * 10]
]
=>
[
   flow-result: [1 2 3]
   flow-result: reverse flow-result
   flow-result: map-each x flow-result [x * 10]
]

But it can't do it ahead of time, because it doesn't know the span of the expressions until it executes them. So it goes one step at a time, inserting the SET-WORD! after each step

[
   flow-result: [1 2 3]  ; <-- gets to here
   reverse flow-result
   map-each x flow-result [x * 10]
]
...
[
   flow-result: [1 2 3]
   flow-result: reverse flow-result  ; <-- gets to here
   map-each x flow-result [x * 10]
]
...
[
   flow-result: [1 2 3]
   flow-result: reverse flow-result
   flow-result: map-each x flow-result [x * 10]  ; finished
]

Very Simple, But Useful Right Off The Bat

I don't know what all design the correct FLOW would need. It would need to do its replacements deeply (we don't have REPLACE/DEEP, so you'd have to use PARSE or something).

And I don't know what features it should have. But... pretty cool to see this kind of thing be so natural to do.

1 Like

Syntax suggestion: I don’t particularly like <$> to denote the hole. I’d suggest _ instead (which is what you already suggested for POINTFREE / PARTIAL, so it’s consistent). Naturally, it should be configurable via a refinement.

1 Like

So there's a uniquely qualified candidate for what could serve as the "nothing here yet", which is ~:

>> flow [
       [1 2 3]
       reverse ~
       map-each x ~ [x * 10]
   ]
== [30 20 10]

What makes it uniquely qualified is that what the quasiform blank ~ evaluates to is the antiform blank, TRASH. And TRASH is not legal as an argument to normal parameters.

TRASH is what represents the unspecified state in frames. So if you did:

 >> f: make frame! :divide
 == #[frame! [
      value1: ~
      value2: ~
 ]]

 >> f.value1: 1020

 >> run f 10
 == 102

You left value2 as antiform blank, which is trash. So it was left unspecified, and gathered in the invocation...making 10 accumulated as value2.

Another helpful out-of-band candidate in this kind of abstraction would be TRIPWIRE!, which could let you make several distinct named entities using meta-tripwires... ~<1>~ and ~<2>~ or ~<before>~ etc.

OF COURSE, USELESSNESS BREEDS POPULARITY... this property may make it more common to use meta-trash/quasi-blank in various quoted places. Configurability would probably still be necessary, and I guess one would have to do some market research to see if people preferred blank as the default just because it looks like a blank.

1 Like