Water 5-Flow Control and Boolean-LoopingThe only looping and iteration method in Water.
| Water Contract<method for_each
include =vector_key
combiner =return_first_arg
exclude =null
for_each_result=opt
forever =false=boolean
_other_unkeyed=opt=wob=ekind.expression="_body"/> | |
Why use for_each?
If you want to execute a body of code multiple times,
perhaps with slight variations, without intervening code
running, you have three choices:
put the code in a method and call it once for each time you want to run ituse recursion, i.e. put the code in a method and have the code call that methoduse for_each
All these techniques are fine programming style in Water,
though there are trade-offs.
Technique 1 is ok when you have a small number of times to run the code
and you know exactly how many there are when you write the code.
Technique 2 is fine provided you're very careful not to
have an infinite recursion and are willing to manage the data
that varies each time around.
Technique 3 gives you some high-level support for varying data,
loop termination and collecting results.
for_each let's you loop a given number of times,
loop over the keys and values of objects and/or
loop until a condition is met.
In many situations, for_each is simpler, more efficient and gives you
higher level control. Using for_each in conjunction with recursion
is a very flexible way to construct "tree walkers" that can descend
complex structures to operate on nested data.
Returned Value Capabilities
Unlike looping mechanisms in lower level languages,
Water's for_each always returns a value. The result
can be the value of the last iteration, or an accumulation
of values from all or just some iterations.
for_each in combination with helper methods lets you
sum values, find the maximum value, return a list of
all or a particular filtered set of values and
many other capabilities. See the description of
the combiner parameter below.
A Simple Example
There are a lot of features in for_each as the length of this documentation
indicates. But you need not read it all to start using for_each .
Here's an example of a simple call that you can generalize
to perform many of the looping tasks you'll need in normal programming.
<set my_colors=<vector "red" "green" "blue"/> />
my_colors.<for_each combiner=insert>
key.<join " is " value/>
</for_each> | <vector "0 is red" "1 is green" "2 is blue"/>
|
Each time around the loop, key.<join " is " value/> is executed
with key bound to 0, 1, and 2 successively and
value bound to "red" , "green" , and "blue" successively.
The results of executing key.<join " is " value/> are
"0 is red" , "1 is green" , "2 is blue" successively.
combiner=insert tells for_each to insert each iteration result into
a vector. It is called combiner since it is actually
combining the new iteration result with the previous ones.
That vector is returned by for_each .
It is equal to <vector "0 is red" "1 is green" "2 is blue"/>
The combiner method defaults to the method return_first_arg
which will cause the value of the last iteration to be returned.
Here we wanted a vector of all iteration results to be returned
which is what combiner=insert gives us.
a wob
| Parameter key | Default value | Type |
| _subject | opt |
The possible items looped over are controlled by the subject of the call to for_each.
a wob_subject=vector
The most common object to loop over is a vector.
Each time around the loop, the local variable 'key' is bound to the index of the
vector element being looped over and the local variable 'value' is bound to
the value of the element.
Looping stops when all the elements of the vector have been looped through.
See the "Simple Example" above for looping through the fields of a vector.
a wob_subject=object
You can loop over all or a subset of the fields within any object.
If a non-vector, non-integer object is the subject in a call to for_each,
it is treated as a 'supervector' and all its non-negative consecutive integer
fields are looped over
unless you have a non-default value for 'include'.
include provides a way for you to select which fields of the object to loop over.
<color red=180 green=10 blue=150/>.<for_each include=regular_key combiner=insert>
key.<join " is " value/>
</for_each> | <vector "red is 180" "green is 10" "blue is 150"/>
|
See below for details on 'include'.
a wob_subject=integer
If the subject of a call to for_each executes to a non-negative integer,
for_each will loop that number of times.
The key and value are bound to the current integer being looped over,
which is between 0 inclusive and the given integer exclusive.
3.<for_each combiner=insert>
key.<join " is " value/>
</for_each> | <vector "0 is 0" "1 is 1" "2 is 2"/>
|
a wob_subject=number.double
If the subject of a call to for_each executes to a double,
it will be rounded to an integer and behave as if that integer
were the subject. This is primarily useful when a math computation
yields something like 3.000001 as often happens with floating point
computation.
2.9.<for_each combiner=insert>
key.<join " is " value/>
</for_each> | <vector "0 is 0" "1 is 1" "2 is 2"/>
|
a wob_subject=optional
If a call to for_each is given no subject, it will error
unless
the 'forever' argument is true. ('forever' defaults to false.)
When there is no subject and forever=true, key and value are not bound
during execution of the content and looping continues until a call to
return is reached. You must have a call to break or return in the content
or else you'll have an infinite loop and your code will never stop
executing.
<for_each forever=true>
<set rand_val=10.<random/>/>
<if> rand_val.<is 5/> <return "we are finished" from="for_each"/>
else rand_val
</if>
</for_each> | "we are finished" |
The 'from' parameter tells what call you are returning from.
Above we specify 'for_each' so the result of the call to
for_each will be the string "we are finished".
We could have chosen to return from the method that our
for_each call was in, or some method that called it, etc, on up the stack.
See below for documentation on the parameter 'forever', and
the method 'return'. 'return' is documented under 'content'.
a wob
| Parameter key | Default value | Type |
| include | vector_key | var |
| include only certain fields to loop over |
When the subject of the call to for_each is an integer,
for_each loops over the integers less than it.
When there is no subject, for_each loops until it executes a call to return.
Otherwise, it loops over the fields of the subject.
By default the object being looped over is treated as a vector and only its integer
fields are looped over starting with field 0 (zero).
Note that a non-vector can have integer fields too and be considered a "supervector".
such as <thing 0="zero" 1="one" weight=99/><thing 0="zero" 1="one" weight=99/>.
<for_each combiner=insert> key </for_each> | <vector 0 1/>
|
Here "weight" is ignored and just 0 and 1 are looped over.
Since the result of executing the content of for_each during each iteration
is simply the value of the 'key' being looped over, and combiner=insert
tells for_each to collect all the iteration results, we get a vector
of 0 and 1.
The "include" argument is usually a method that determines exactly which fields to select
for processing.
Water provides several methods that were made expressly for the value
of include:
a wobinclude=vector_key
(the default) Selects the integer-named fields in a vector.
This method is handled specially by for_each for performance and to
process only those integer fields from 0 to just under the length of the subject.
a wobinclude=string_key
Selects all the keys that are strings but are not system_key or meta_key named fields.
a wobinclude=regular_key
Selects fields that are either vector_ley or string_key named fields.
a wobinclude=system_key
Selects keys that are strings that don't start with underscore.
Used to select fields such as "_parent". Rarely used.
a wobinclude=meta_key
Selects only field names that are strings that contain an _f_ substring.
a wobinclude=non_system_key
Selects all non_system field keys, thus it passes string_keys,
vector_keys, meta_keys, and negative integer fields.
a wobinclude=return_true
Selects all fields.
Our example above has include default to vector_key.
If we wanted to include "weight" we'd use include=regular_key like so:
<thing 0="zero" 1="one" weight=99/>.<for_each include=regular_key combiner=insert>
key
</for_each> | <vector "weight" 0 1/>
|
Here's how to exclude the integer keys, system keys and anything that's not a string key.
<thing 0="zero" 1="one" weight=99/>.<for_each include=string_key combiner=insert>
key
</for_each> | <vector "weight"/>
|
You can write your own methods to include other fields than the above methods allow.
The method is called with each key as _subject
If the method returns false, the field will not be processed, otherwise it will be.
<method is_string_starting_with_a>
string.<is_type_for _subject/>.<and
_subject.<length/>.<more 0/>
_subject.0.<is <char "a"/>/>/>
</method>
<thing apple="red" apricot="orange" banana="yellow"/>.
<for_each include=is_string_starting_with_a combiner=insert>
key
</for_each>.<sort/> | <vector "apple" "apricot"/>
|
Note that the order of processing non-vector keys is undefined so
when dealing with string keys or any unusually keys, be prepared to
handle them in any order. The above might return
<vector "apple" "apricot"/>
instead of <vector "apricot" "apple"/>
If you want to process the fields of an object in a sorted order,
here's one way:
<set fruits=<thing apple="red" apricot="orange" banana="yellow"/>>
fruits.<keys/>.<sort/>.<for_each combiner=insert> value </for_each>
</set>
 | <vector "apple" "apricot" "banana"/>
|
Note above that the "value" in the content of the for_each call is really
a key from the fruits object. To turn that key back into the real value
of a field from the fruits object:
<set fruits=<thing apple="red" apricot="orange" banana="yellow"/>>
fruits.<keys/>.<sort/>.<for_each combiner=insert> fruits.<get value/> </for_each>
</set>
 | <vector "red" "orange" "yellow"/>
|
Above we have effectively sorted the colors by the keys they are associated with.
a wobinclude=vector
The value of include can also be a vector listing the fields to include.
Only those fields that are in the include vector will be "looped over".
If any fields are in the include vector but NOT in the subject, then
they are simply ignored. Furthermore the order of the looping will be the order
of the items in the include vector.
<vector "zero" "one" "two"/>.<for_each include=<vector 1 0 99/> combiner=insert>
value
</for_each> | <vector "one" "zero"/>
|
a wobinclude=type
If the value of "include" is NOT a method or a vector, then it is considered to be a type.
If the field key being tested is of that type, then it will be looped over,
otherwise it will be skipped. Below only the fields 2.5 and 3.5 are chosen,
and since our combiner is plus, the result is 6.0.
<thing 1="one" 2.5="twopointfive" 3="three" 3.5="threepointfive"/>.
<for_each include=number.double combiner=plus>
key
</for_each> | 6.0 |
<vector "zero" "one" "two" "three"/>.<for_each include=integer.even combiner=insert>
value
</for_each> | <vector "zero" "two"/>
|
a wob
| Parameter key | Default value | Type |
| exclude | null |
The exclude parameter, like the include parameter, can take a method,
a vector, or a type.
If a field name passed the include test, then it is tested against the exclude parameter.
If the field name matches what is to be excluded, then the field is skipped.
Think of exclude as having veto power over a key that passes the include test.
The default value for exclude is the method returns_true meaning nothing is excluded.
<vector 100 101 102 103 104/>.
<for_each combiner=insert exclude=<vector 1 3/>> key </>
 | <vector 0 2 4/>
|
Below is achieves the same result as an example in the "include" section
that uses include=number.double Here we are including all the keys
except the "_parent" key yielding 1, 2.5, 3, and 3.5,
then we are excluding from that set the
integer keys until we are left with just 2.5 and 3.5, which are summed
to get 6.0.
<thing 1="one" 2.5="twopointfive" 3="three" 3.5="threepointfive"/>.
<for_each include=non_system_key exclude=integer combiner=plus>
key
</for_each> | 6.0 |
a wob
| Parameter key | Default value | Type |
| forever | false | boolean |
If forever is true, that tells for_each to continue looping after it would normally be
done. One way to use it is to make multiple passes over fields in an object or to
loop through the integers less than the subject integer multiple times.
First here's an example with forever=false, which is the default value.
<set mycount=0/>
2.<for_each combiner=insert forever=false>
<if> mycount.<is 5/> <break for_each_result/>
else <do <set mycount=mycount.<plus 1/> /> value/>
</if>
</for_each> | <vector 0 1/>
|
We loop through 0, and 1, then stop because the subject of 2 sets the limit.
Next here's the same example with forever=true:
<set mycount=0/>
2.<for_each combiner=insert forever=true>
<if> mycount.<is 5/> <break/>
else <do <set mycount=mycount.<plus 1/> /> value/>
</if>
</for_each> | <vector 0 1 0 1 0/>
|
The execution loops through 0 and 1, but because forever=true it doesn't stop,
It does the same loop again and even gets into a third time before
stopping due to the call to break.
If we have no subject to our call to for_each, it will error if forever=false.
But if forever=true, it loops until a call to break.
<set mycount=0/>
<for_each combiner=insert forever=true>
<if> mycount.<is 5/> <break/>
else <do <set mycount=mycount.<plus 1/> /> mycount/>
</if>
</for_each> | <vector 1 2 3 4 5/>
|
Note that in the above example with no subject, key and value are not bound
when the content of the for_each is executed so we can't reference them.
a wob
| Parameter key | Default value | Type |
| content | false |
The content of for_each is executed once for each iteration of the loop.
During the execution:
-
it is bound to the object whose fields are being looped over -
key is bound to the name of the field within "it" currently being looped over and -
value is bound to the value of that field -
for_each_result is bound to the accumulated result so far.
(See the documentation for the "combiner" parameter.)
Other local variables that are bound before the call to for_each,
but in the same lexical scope (such as the same method body) will still be
bound each time the body of the for_each is executed.
_subject is still bound to _subject of the method call of the method
that the for_each call is in.
a wobcontent=skip
If the last expression of an iteration is the variable "skip",
then no value is "accumulated" into for_each_result for this iteration.
skip is useful when you want to filter-out certain values from the returned result.
Below we filter out all instances of 4 from the vector.
<vector 2 4 6 4 8 4/>.<for_each combiner=insert>
<if> value.<is 4/> skip
else value
</if>
</for_each> | <vector 2 6 8/>
|
How To Stop Looping
There are several ways to stop looping. It is important to understand them all.
The normal way is to just let for_each go through all of the elements that
it is designated to loop over based on the subject and the include and exclude attributes.
That is documented above under the documentation about the subject, include and exclude.
But sometimes you discover, dynamically that you want to end the loop.
If you have no subject and forever=true, you MUST explicitly end the loop.
a wobcontent=break
The primary way of doing this is to call
<break/>
If you call break with no arguments or an argument of skip, i.e.
<break skip/> ,
then iteration will stop and for_each will return the value of what for_each_result was before the
call to break.
5.<for_each combiner=insert> <if> value.<is 3/> <break/>
else value
</if>
</for_each> | <vector 0 1 2/>
|
If you call break with an argument, that argument will supply the
value of the current iteration and it will be combined with the combiner into
for_each_result and the new value of
for_each_result will be returned.
5.<for_each combiner=insert> <if> value.<is 3/> <break 99/>
else value
</if>
</for_each> | <vector 0 1 2 99/>
|
Use
<break/> when you have done one iteration too many and want to end the loop with
what has already been computed in
for_each_result.
use
<break "some val"/> when you know you've reached the last iteration and want to
add one last value to for_each_result before returning
for_each_result.
a wobcontent=return
Most of the time,
<break/> is the best way to stop looping
for_each early.
But when you want to return a specific value that has nothing to do with
for_each_return,
or you want to return from more than the current_for_each call, calling
return
can be very handy.
A call to
return can appear anywhere in Water code, but it is particularly useful
in the content of a call to for_each for exiting the loop early.
Example: Find the first number greater than 4
<vector 2 4 6 8/>.<for_each>
<if> value.<more 4/> <return value from="for_each"/> </if>
</for_each> | 6 |
If you have nested calls to
for_each, a return from the inner one will
leave the code still executing the outer one. To have a call to return
exit from the outer one, have the
from value not be
"for_each",
but rather the name of the method that contains the outer for_each call.
<method find_5th_leaf vec=req>
<set mycount=0/>
vec.<for_each>
<if>
vector.<is_type_for value/>
value.<for_each>
<set mycount=mycount.<plus 1/>/>
<if> mycount.<is 5/> <return value from="find_5th_leaf"/> </if>
</for_each>
mycount.<is 5/>
<return value from="find_5th_leaf"/>
else <set mycount=mycount.<plus 1/>/>
</if>
</for_each>
</method>
<find_5th_leaf <vector 0 10 <vector 20 30 40 50/> 60/>/> | 40 |
Below is a difference approach to returning from the inner level of
a nested call to
for_each. Instead of returning from the surrounding
method, we set the value to ultimately return to a local variable
(in this case
the_result). We are now free to use the value returned
by 'return' to be a control value (in this case,
"done").
This string has no special significance, we just check for it in our
outer
for_each call and if we find it, return from that outer
for_each call as well.
<method find_5th_elt vec=req>
<set the_result=null/>
<set mycount=0/>
vec.<for_each>
<if> vector.<is_type_for value/>
<if> value.<for_each>
<set mycount=mycount.<plus 1/>/>
<if> mycount.<is 5/>
<do <set the_result=value/>
<return "done" from="for_each"/>
/>
</if>
</for_each>.<is "done"/>
<return from="for_each"/>
</if>
mycount.<is 5/>
<do <set the_result=value/>
<return from="for_each"/>
/>
else <set mycount=mycount.<plus 1/>/>
</if>
</for_each>
the_result
</method>
<find_5th_elt <vector 0 10 <vector 20 30 40 50/> 60/>/> | 40 |
Note that find_5th_elt is only good for working on data of depth 2.
See below on walking trees for more general algorithms that can
descend arbitrarily deep into nested vectors.
infinite loop
If you do accidentally create an infinite loop and you're in the
Steam development environment, just click on the "Cancel" button
to stop looping.
a wob
| Parameter key | Default value | Type |
| for_each_result | opt |
| holds the value returned by for_each |
Looping constructs in many languages return no values. Java's and C's for loops are
an example. In Water there are no "void" methods. Every call returns a value.
Not only does this make the language more uniform, it makes it more
useful as well. You can, of course, choose to ignore the returned values,
but returned values from Water's control methods such as if and for_each
can save you a lot of work.
By default, for_each returns the value of executing the content of the for_each
call the very last time it is executed.
5.<for_each> value </for_each>
 | 4 |
You can change what for_each returns by giving the combiner argument a method.
Each time through the loop this method will be called with the
previous result, stored in the for_each_result variable,
as the subject and the result of the current iteration as the first argument.
The result of that call is stored back into for_each_result.
Effectively here's what happens at the end of each iteration:
<set for_each_result=for_each_result.<<do combiner/> current_iteration_result/>/>
The value of for_each_result is returned from the call to for_each
after the last iteration is executed and the value of for_each_result
is updated as a result of it.
But this begs the question, how do we get the initial value of for_each_result?
You can supply it with the for_each_result argument.
<vector 5 7/>.<for_each combiner=insert for_each_result=<vector 1 3/>>
value
</for_each> | <vector 1 3 5 7/>
|
for_each_result is also useful to supply a result for a call to for_each that
has no iterations.
0.<for_each for_each_result=99> value </for_each>
 | 99 |
We would never write a call to for_each with an explicit subject of zero since
that is guaranteed to have no iterations. But if the number of times to iterate
is computed by our program, or some combination of the include and exclude
arguments just so happen to exclude all iterations, initializing for_each_result can
come in very handy.
For many of the most useful methods that we will want to use for the value
of the combiner argument, there is a natural initial value for for_each_result.
For example, when we are using combiner=insert, the natural for_each_result is
an empty vector. If we are using plus to add up the value of each iteration,
the natural initial value for for_each_result is 0.
We can supply these natural values
explicitly and that's fine, but there is an easier way.
for_each_result has a "smart default" that depends on the combiner method.
At the end of each iteration, the combiner method is called (unless the
iteration result is skip with for_each_result as the subject
and the iteration result as the first arg.
On the first iteration, for_each_result, unless it was explicictly passed in,
will be optional. All of the standard methods used as combiners return something
sensible when called with an optional subject and a first arg.
return_first_arg, plus, times, minimum, maximum,
and, or, and join all return
their first arg. append returns a copy of its first arg.
insert returns a vector with one element, the first arg.
Once we're done with the first iteration, for_each_result will have a real value
and it will be used as the subject to successive calls to the combiner method.
However, if no iterations happen, then the combiner is called with no subject
and no arguments and the result of that call is returned by for_each.
Again, all of the usual methods to use as combiners return sensible results:
return_first_arg returns nullplus returns 0times returns 1minimum returns the largest integermaximum returns the smallest integerand returns trueor returns falsejoin returns the empty stringinsert returns an empty vector
a wob
| Parameter key | Default value | Type |
| combiner | return_first_arg | var |
| A method that combines interation results into for_each_result |
The methods used as values for for_each's combiner argument can be any
Water method. Many of the most useful are Water methods that you'll use
outside of the context for for_each as well.
One of the most common methods to use as a value of combiner is "insert".
Here's a normal call to insert:
<vector 3 5/>.<insert 7/>
 | <vector 3 5 7/>
|
Using combiner=insert, you can accumulate the value of
each iteration into a vector and return that vector from the call to for_each
like so:
<vector 3 5 7/>.<for_each combiner=insert> value.<plus 1/> </for_each>
 | <vector 4 6 8/>
|
At the end of each iteration, insert is being called with the value of the
iteration ( 4, 6 and 8 successively) and being inserted into the vector
that is the value of for_each_result.
Using combiner=insert is useful as a debugging tool since it can show you the result
of each iteration. More generally, think of insert as a way to copy elements
of a structure, or copy modified version of elements as in our example above
where we are essentially copying the elements of the vector but adding one
to each just before we insert them into the new vector.
You needn't copy all of the elements of the subject, you can select which ones
using the include argument or explicitly exclude certain ones using the
exclude argument. (See documentation above.) An additional way to
filter out undesired values is to have the last expression executed
in an iteration be the variable skip.
<vector 4 11 5 12 6 13/>.<for_each combiner=insert>
<if> value.<more 10/> value
else skip
</if>
</for_each> | <vector 11 12 13/>
|
You can use combiner to sum up the iteration values, find the minimum or maximum
values and an unlimited number of other possibilities.
It is called combiner because they all, in some sense, combine the
current iteration result with the existing value of for_each_result
to form a new value for for_each_result.
Many more examples of using combiner are below.
Writing your own combiner method
If you are writing your own combiner method, either make sure that it
can be called with no subject and no first argument as well as
no subject and a first arg, or always supply
a for_each_result when you use the method in a call to for_each.
<method double_then_add _subject=0 arg=0>
arg.<times 2/>.<plus _subject/>
</method>
<double_then_add/> | 0 |
<vector 3 10/>.<for_each combiner=double_then_add> value </for_each>
 | 26 |
In the above example,
first <double_then_add/> is called to yield
0 which is bound to for_each_result
then for_each_result.<double_then_add 3/> is called to yield
0 + (3 * 2) equals 6 which is bound to for_each_result.
then for_each_result.<double_then_add 10/> is called to yield
6 + (10 * 2) equals 26 which is returned.
We could have achieved the same functionality with:
<vector 3 10/>.<for_each
combiner=<method null arg=req> arg.<times 2/>.<plus _subject/></>
for_each_result=0
>
value
</for_each> | 26 |
Note that <method arg=required> "..." </> makes an unnamed method.
Had we done <method arg> "..." </> arg would have been interpreted as the
name of the method which would take no arguments.
We could have also defined it <method foo arg=req> "..." </> but
since we are intending it to just be called from this one place,
we might not want to redefine some other method previously named fooa wobcombiner=return_first_arg
The default value for combiner is the method
return_first_arg.
All this method does is return its first argument.
If there is no subject and no first argument, it returns null.
Using
combiner=return_first_arg causes
for_each to return the value
of the last iteration whose value was not
skip.
In the below example,
combiner defaults to
return_first_arg.
Our content code is called 5 times, but only for the first three
do we call
return_first_arg to accumulate the result into
for_each_result
because for the iterations with values
12 and
15, the result is
skip.
<vector 3 6 9 12 15/>.<for_each>
<if> value.<more 10/> skip
else value
</if>
</for_each> | 9 |
<return_first_arg/> returns null which is the smart default for
for_each_result.
a wobcombiner=insert
combiner=insert collects the value of each iteration into one vector and returns it.
<insert/> is
<vector/> which is the smart default for
for_each_result.
The documentation for
combiner has example calls to
for_each using
combiner=insert.
Use
insert in conjunction with
skip to filter values out of the resulting vector.
a wobcombiner=append
append is similar to
insert except that you give it a vector of values
to append to the subject. Using
combiner=append lets you add a variable number
of elements to
for_each_result for each iteration.
Note that using
combiner=append with an iteration value of the empty vector
is like using
combiner=insert with an iteration value of
skip.
<append/> returns
<vector/> which is the smart default for
for_each_result.
<vector 3 6 9 12 15/>.<for_each combiner=append>
<if> value.<less 10/> <vector key value/>
else <vector/>
</if>
</for_each> | <vector 0 3 1 6 2 9/>
|
Note that append always returns a new vector so even if you call
for_each
with
combiner=append and
for_each_result=foo where
foo is a
vector,
foo will not be modified by the call to
for_each but will return
a vector whose first items are the same as foo's.
a wobcombiner=plus
combiner=plus lets you compute the sum of the results of each iteration.
<plus/>
0 which is the smart default for for_each_result.
The below example performs 0 + 0 + 1 + 2 + 3 and returns 6.
4.<for_each combiner=plus> value </for_each>
 | 6 |
a wobcombiner=times
combiner=times lets you compute the product of the results of each iteration.
<times/>
1 which is the smart default for for_each_result.
The below example performs 1 * 2 * 3 * 4 and returns 24.
<vector 2 3 4/>.<for_each combiner=times> value </for_each>
 | 24 |
a wobcombiner=minimum
combiner=minimum lets you find the smallest of the results of each iteration.
<minimum/>
integer.max_value which is the smart default for for_each_result.
<vector 5 3 99 7/>.<for_each combiner=minimum> value </for_each>
 | 3 |
a wobcombiner=maximum
combiner=maximum lets you find the largest of the results of each iteration.
<maximum/>
integer.min_value which is the smart default for for_each_result.
<vector 5 3 99 7/>.<for_each combiner=maximum> value </for_each>
 | 99 |
a wobcombiner=and
combiner=and lets you determine if all the iteration results are not false.
<and/>
true which is the smart default for for_each_result.
and considers any non-false value to be "true".
If all of the values are non-false, it returns the last argument to 'and',
else it returns false.
So
2.<and 3/>
3 ...and result is not true.
This let's 'and' return a potentially more useful result than true.
Since the test clauses of 'if' work off of false and non-false,
and works well with 'if' as well as methods that require more than a boolean.
<vector true true false true/>.<for_each combiner=and> value </for_each>
 | false |
<vector true true thing 27/>.<for_each combiner=and> value </for_each>
 | 27 |
a wobcombiner=or
combiner=or lets you determine if at least one of the iteration results is not false.
<or/>
false which is the smart default for for_each_result.
'or' considers any non-false value to be "true".
or returns the first non-false argument or false.
So
2.<or false/>
2
...result is not true.
<vector true true false true/>.<for_each combiner=or> value </for_each>
 | true |
<vector false false false/>.<for_each combiner=or> value </for_each>
 | false |
<vector false false 34/>.<for_each combiner=or> value </for_each>
 | 34 |
a wobcombiner=join
combiner=join lets for_each return one big string containing a
concatenation of all the strings.
<vector "hey" "you"/>.<for_each combiner=join> value</for_each>
 | "heyyou" |
Calling join with no arugments and no subject:
<join/>
""
gives us the smart default for
for_each_result
Note that when using
combiner=join returning a value of skip
from an iteration is the same as returning a value of
"".
join calls to_string on the values it is about to join so in order to
get a string of html we must call to_htm on the value first.
<vector 3 <b>hi</b> "you"/>.<for_each combiner=join> value.<to_htm/></>
 | "3<b>hi</b>you" |
Note that when using
combiner=join returning a value of
skip from an iteration is the same as returning a value of
"" for_each Caveats
When you are looping over the fields of the subject in a call to for_each,
it is recommended that you not delete or add fields to that subject
in the content of the for_each.
For non-vector_keys, you can't
be sure of the order that looping will occur so be careful
if you want to modify the values of fields you're looping over as
that might have results that you don't expect.
Fibonacci Examples
Computing a Fibonacci series is one of those standard algorithms
used to demonstrate how a programming language works.
The Fibonacci series is a series of integers whose first integer
is 0, second integer is 1, and each integer thereafter is the sum
of the previous two integers in the series.
Below we have a number of examples showing different ways
of using for_each to compute the Fibonacci series of length 10.
For our first example we use the length of for_each_result
as our "counter" that tells us what number of the sequence we're on.
This enables us to special case the first, second and last iterations.
Since we are calling for_each with no subject and forever=true,
we must have a call to return. It is executed on the last iteration.
The result is combined using 'insert' into for_each_result to
produce the vector of integers we desire.
<for_each combiner=insert forever=true>
<set len=for_each_result.<length/>/>
<if> len.<is 0/> 0
len.<is 1/> 1
len.<is 10 /> <return for_each_result from="for_each"/>
else <plus for_each_result.<get len.<minus 1/>/>
for_each_result.<get len.<minus 2/>/>/>
</if>
</for_each> | <vector 0 1 1 2 3 5 8 13 21 34/>
|
Next we supply an for_each_result of <vector 0 1 /> to handle
our first and second "special cases".
We will only have 8 iterations in this case making our code faster
as well as smaller.
<for_each combiner=insert forever=true for_each_result=<vector 0 1 /> >
<set len=for_each_result.<length/>/>
<if> len.<less 10 />
<plus for_each_result.<get len.<minus 1 /> />
for_each_result.<get len.<minus 2 /> /> />
else
<return for_each_result from="for_each" />
</if>
</for_each> | <vector 0 1 1 2 3 5 8 13 21 34/>
|
Next we have a much different algorithm. We are using the length of
the series we want to compute as the subject, i.e. 10.
Upon the first iteration, value is bound to 0 and on the second
it is bound to 1, so we can use the value of 'value' to
supply our first and second result elements.
The trick of value.<less 2/> saves us one 'if' clause over the
first example. We save another if clause, i.e. the check for
"done" by having for_each do that for us automatically by virtue
of the fact that we have an integer as the subject of our call to for_each.
Note that we are replacing
for_each_result.<get len.<minus 1/> />
with
for_each_result.<last 1/>
and
for_each_result.<get len.<minus 2/> />
with
for_each_result.<last 2/>
This avoids us having to calculate the length of for_each_result
altogether. Note that the first argument to last defaults to 1
so we could have said <last/> instead of <last 1/>
but making it explicit reads well.
10.<for_each combiner=insert>
<if> value.<less 2/>
value
else
<plus for_each_result.<last 0/>
for_each_result.<last 1/> />
</if>
</for_each> | <vector 0 1 1 2 3 5 8 13 21 34/>
|
Below we combine the above for_each example with using for_each_result.
Since we have already supplied the first two result elements,
we need to be sure to 'skip' adding them onto for_each_result.
10.<for_each combiner=insert for_each_result=<vector 0 1/>>
<if> value.<less 2/>
skip
else
<plus for_each_result.<last 0/>
for_each_result.<last 1/> />
</if>
</for_each> | <vector 0 1 1 2 3 5 8 13 21 34/>
|
The inefficiency with the above example is that we needlessly execute the
first two iterations and just 'skip' what they could have computed.
Because we're using last, and our special-casing of the first two items
is handled via for_each_result=<vector 0 1/> and using an
integer for the subject automatically provides the ending condition,
we don't need to know what number of the series we are computing within
each iteration. Thus we are just using the integer subject to
declare the number of iterations, nothing more.
All of the special-casing is handled by the built-in machinery of for_each.
However, we need two less
iterations than the length of our result series so we just
subtract 2 from the length of the series for a subject of 8
and end up with a very compact algorithm.
10.<minus 2/>.<for_each combiner=insert for_each_result=<vector 0 1/>>
<plus for_each_result.<last 0/>
for_each_result.<last 1/> />
</for_each> | <vector 0 1 1 2 3 5 8 13 21 34/>
|
The second argument to 'last 'defaults to just returning one element,
but had we wanted to return
the last 3 elements of a vector we could say:
<vector 10 11 12 13 14 15/>.<last 2 "all"/>
 | <vector 13 14 15/>
|
Finally we're able to change our two-liner body into a one-liner
by taking advantage of the 2nd argument to 'last'.
10.<minus 2/>.<for_each combiner=insert for_each_result=<vector 0 1/>>
<plus _other_unkeyed=for_each_result.<last 1 "all"/>/>
</for_each> | <vector 0 1 1 2 3 5 8 13 21 34/>
|
Along with Fibonacci you'll also see computing the factorial of a
number as a classic Software 101 algorithm.
This is the number times one less than the number times
two less than the number ... all the way down to 1.
Factorial of 3 is 3 times 2 times 1 equals 6.
Factorial of 4 is 4 times 3 times 2 times 1 equals 24.
Water's for_each makes this a one-liner.
Example: Factorial
3.<for_each combiner=times> value.<plus 1/> </>
 | 6 |
Since value will be bound to 0, 1, and 2, we need to shift it up
one to have the result of each iteration be 1, 2 and 3 successively.
The order of our multiplies doesn't matter but we can't have
a zero in there or our result will be zero. When using combiner=times,
our initial result will default to <times/> which is 1.
So in the above example we start with 1, multiply 1 by 1 to yield 1,
then 1 by 2 to yield 2, then 2 by 3 to get 6.
Walk Tree Examples
for_each can be used to "walk trees", i.e. descend down into
deeply nested data structures to test conditions, find particular values,
modify values, or collect a variety of data to return.
The examples below follow a particular pattern with is very flexible when
dealing with nested data, especially when that data is fairly uniform.
The data that we are walking in each example is a vector containing
integers and other vectors containing integers, though this can certainly
be generalized to walk objects and their fields using the 'include'
argument to for_each.
Each example defines a method that takes
the nested vector to walk (which becomes the subject to the call to for_each)
and sometimes the inital_result which becomes the value of for_each_result
in the call to for_each.
The body of the method consists of one call to for_each.
The combiner argument in for_each changes depending on the example.
The content of the call to for_each contains a call it 'if'
which has two clauses. One clause test to see if the value being looped over
is a leaf (in this case a number).
If so the value is returned, sometimes
after being modified. If the value is not a leaf we "recurse" by calling
the method we have defined. The method is called with 'value' which
will be a vector to do further processing on.
This pattern sounds fairly restricted, yet with the flexibility of for_each
and Water allows us to make a surprisingly large variety of computations.
Our most basic example just copies the tree it is passed.
This isn't too useful by itself, but you'll see how we build on this theme
in later examples to derive lots of other results.
<method copy_tree vec=req>
vec.<for_each combiner=insert>
<if> value.<is_a number/> value
else <copy_tree value/>
</if>
</for_each>
</method>
<copy_tree <vector 2 <vector 1 <vector 5 6/>/> 4/>/> | <vector 2 <vector 1 <vector 5 6/>/> 4/>
|
Note that for this case, instead of value.<is_a number/> to test
whether we have a leaf, we could have used value.<is_a vector/>.<not/>
In general, though, what is a leaf and what isn't depends on how you want to
treat your data.
Below instead of making an exact copy, we're modifying each
element in the new tree with: value.<times 10/> Example: copy the tree but multiply each number by 10
<method copy_tree_times_10 vec=req>
vec.<for_each combiner=insert>
<if> value.<is_a number/> value.<times 10/>
else <copy_tree_times_10 value/>
</if>
</for_each>
</method>
<copy_tree_times_10 <vector 2 <vector 1 <vector 5 6/>/> 4/>/> | <vector 20 <vector 10 <vector 50 60/>/> 40/>
|
The below example is exactly the same as the original copy_tree,
except that it has the non-default value for combiner of 'plus'.
Since <plus/> is 0, for_each_result starts with 0.
We add each number in the tree to it for a grand total of 10.
Example: sum all the items in the nested vectors
<method sum_tree vec=req >
vec.<for_each combiner=plus>
<if> value.<is_a number/> value
else <sum_tree value/>
</if>
</for_each>
</method>
<sum_tree <vector 2 <vector 1 <vector 1 1 1/>/> 4/>/> | 10 |
The below example is the same as our plus example with 'plus'
replaced by the method 'maximum'. Instead of adding the numbers
together we simply find the highest one and return it.
Example: find the max item in the nested vectors
<method find_max_in_tree vec=req >
vec.<for_each combiner=maximum>
<if> value.<is_a number/> value
else <find_max_in_tree value/>
</if>
</for_each>
</method>
<find_max_in_tree <vector 2 <vector 1 <vector 5 6/>/> 4/>/> | 6 |
Use the method 'minimum' to find the lowest value. If you want to
find the average value, you can use plus to compute their sum, then
use the count_leaves to find the number of numbers, then divide the
sum by the count to make the average.
That approach makes two passes over the tree.
Another approach is to use 'flatten_tree' (see example below)
to make a single vector of all the numbers, then
sum them with plus and divide by the length of the flattened_vector like so:
<set flat_vec=<flattened_tree nested_vec/>>
<plus rest=flat_vec/>.<divide flat_vec.<length/>/>
</set>
Below we are searching for a value that's "good enough".
Once we find it we don't care if there are better values
in the remainder of the tree
so we want to exit our tree walk early to make the
computation faster. We call 'return' with its 'from' argument equal to
the name of the method to exit.
We need to construct two methods so that we can return
from the non-recursive outer method to get out of deeply nested
calls to the inner method. If we had just one method,
when we returned, we'd just get out of one nesting of the
method and continue on without exiting all levels of the method.
Example: search_tree_and_return_early
<method find_first_number_over_4 vec=req >
<find_first_number_over_4_aux vec/>
false
</method>
<method find_first_number_over_4_aux vec=req >
vec.<for_each>
<if> value.<is_a number/>
<if> value.<more 4/> <return value from="find_first_number_over_4"/> </if>
else <find_first_number_over_4_aux value/>
</if>
</for_each>
</method>
<find_first_number_over_4 <vector 2 <vector 1 <vector 7 8/>/> 4/>/> | 7 |
Below we want to count the leaves. Our trick is to use the same
code for summing all the values, only instead of summing
the actual values we just sum 1 for each leaf.
Example: count leaves in tree
<method count_leaves vec=req >
vec.<for_each combiner=plus>
<if> value.<is_a number/> 1
else <count_leaves value/>
</if>
</for_each>
</method>
<count_leaves <vector 2 <vector 1 <vector 7 8/>/> 4/>/> | 5 |
Below we are essentially doing a count but instead of always
feeding 1 to plus we feed 0 to it when we want
that value (any value 4 and under) to not count.
Example: count leaves in tree more than 4
<method count_leaves_more_than_4 vec=req >
vec.<for_each combiner=plus>
<if> value.<is_a number/>
<if> value.<more 4/> 1 else 0 </if>
else <count_leaves_more_than_4 value/>
</if>
</for_each>
</method>
<count_leaves_more_than_4 <vector 2 <vector 1 <vector 7 8/> 9/> 4/>/> | 3 |
When we want to operate on just the leaves of a tree,
its often much easier to deal with a flat vector of
objects than a tree nested to arbitrary depth.
The example below shows how to create a flat vector
out of a tree. This is rarely an end in itself, but
rather a key stepping stone to other computations.
The secrete to flatten_tree is to pass down the for_each_result
created with the first call of flatten_tree into the
recursive calls to flatten_tree. Just one vector is created
for the value of for_each_result and added to by all levels.
Example: flatten a nested tree
<method flatten_tree vec=req for_each_result=<vector/>>
vec.<for_each combiner=insert for_each_result=for_each_result>
<if> value.<is_a number/> value
else <do <flatten_tree value for_each_result/> skip/>
</if>
</for_each>
</method>
<flatten_tree <vector 2 <vector 1 <vector 5 6/>/> 4/>/> | <vector 2 1 5 6 4/>
|
Below we have another version of flatten that's the same as the above
example except that we modify the value stored in the result vector
with the code value.<times 10/> Example: flatten and modify each leaf
<method flatten_tree_times_10 vec=req for_each_result=<vector/>>
vec.<for_each combiner=insert for_each_result=for_each_result>
<if> value.<is_a number/> value.<times 10/>
else <do <flatten_tree_times_10 value for_each_result/> skip/>
</if>
</for_each>
</method>
<flatten_tree_times_10 <vector 2 <vector 1 <vector 5 6/>/> 4/>/> | <vector 20 10 50 60 40/>
|
Below we have another version of flatten that doesn't store
all values in the returned vector, just those that
are more than 4. We return 'skip' from those iterations
that have a lower value to filter out those values.
Example: find numbers more than 4 in tree
<method numbers_more_than_4 vec=req for_each_result=<vector/>>
vec.<for_each combiner=insert for_each_result=for_each_result>
<if> value.<is_a number/>
<if> value.<more 4/> value else skip </if>
else <do <numbers_more_than_4 value for_each_result/> skip/>
</if>
</for_each>
</method>
<numbers_more_than_4 <vector 7 <vector 1 <vector 5 6/>/> 4/>/> | <vector 7 5 6/>
|
© Copyright 2007 Clear Methods, Inc.