Each versus for-each and for-all

In 8th, they have the *:each as a higher order function.

So, depending on whether it's being applied to a map, array, buffer, string it works like this:

Buffer lambda b:each

So, here the buffer is iterated over and the index and value are placed on the stack for lambda to consume.

For-each neesd to create words, and for-all needs to reference the series by name which creates more visual clutter.

In VID lambdas had access to a similar functionality where value was available.

We could do the same:

Each series [ code ] 

Where code has access to the words index and value of the series being traversed.

1 Like

This old post raises a good point about wanting access to both the position and value easily from the same loop.

The concept of assuming the names (value, index) is something definitely needed in Rebmu. I'm a bit reticent to put it in a "core" function. But we should make it trivially easy to make such operators if you want it.

I had a random brainstorm today about how EACH might be a generator.

>> g: each x [1 2] [print ["running" x], x + 10]

>> g
running 1
== 11

>> g
running 2
== 12

>> g
null

A thought I had was we might then imagine FOR as something that calls a function until it returns NULL, and returns the last non-NULL thing.

>> for each x [1 2] [print ["running" x], x + 10]
running 1
running 2
== 12

Then MAP could call the function until it returns NULL, but return the results:

>> map each x [1 2] [print ["running" x], x + 10]
running 1
running 2
== [11 12]

This thought is definitely more "minecraft of programming"...breaking FOR and MAP and EACH down into generic bricks.

Though there are a couple of sticking points with it... e.g. it doesn't generalize arbitrarily to things like remove each...and if NULL is taken to signal "done" then it can't also signal BREAK. But I'm now thinking maybe generators can return NULL if they speak a multi-return protocol...where NULL is augmented with "/BRANCHED". That's looking really desirable now.

Makes for interesting possibilities:

>> map generator [count-up x 3 [yield x], count-down 3 [yield x]]
== [1 2 3 3 2 1]

FOR would be similar to UNTIL .NULL?...but if you said until .null? [ask integer!] it would keep asking for integers until you canceled, then it would return NULL. But for [ask integer!] would keep asking and then give you the last input. map [ask integer!] would give you the list of integers.

2 Likes

Something else I've been thinking about (that @BlackATTR has brought up in the past) is the fact that you often want to know when you're on the first or last step of an iteration.

There could be some sensitivity in EACH to whether the code it is running has a /FIRST or /LAST refinement (which bolsters my point about first of and last of):

>> e: each x [1 2 3] func [value /first /last] [
    if first [print "first!"]
    print [value]
    if last [print "last!"]
]

>> for :e
first!
1
2
3
last!

This could be shortened with a lambda syntax:

>> for each x [1 2 3] value/first/last -> [
    if first [print "first!"]
    print [value]
    if last [print "last!"]
]

first!
1
2
3
last!

But maybe it's EACH that should be parameterized, and they should be normal arguments, so you don't get pinned to the names:

>> for each/first/last x [1 2 3] [val fst lst] -> [
    if fst [print "first!"]
    print [value]
    if lst [print "last!"]
]

first!
1
2
3
last!

There could be a lambda shorthand for that too, maybe TUPLE!

>> for each/first/last x [1 2 3] val.fst.lst -> [
    if fst [print "first!"]
    print [value]
    if lst [print "last!"]
]

first!
1
2
3
last!

Just one of the kinds of axes to explore with generalized EACH.

2 Likes