A collection is a data structure that stores multiple values. Acorn's core library offers these collection types:
- Text. A sequence of Unicode characters.
- List. An ordered collection of values.
- Index. An unordered collection of key/value pairs.
- Type. An unordered collection of inheritable key/value pairs.
Additional collection types can also be defined, as needed.
The Acorn language offers unique facilities to simplify snapping collections together into rich static or procedural content:
- 'this' blocks and operators are used to add or alter specific values within a specific collection (including custom types). 'this' blocks can be nested, allowing assembly of complex, multi-layered collections.
- 'each' blocks iterate through the individual values of a collection. Rich collection "comprehensions" are easily specified by using 'each' blocks within 'this' blocks.
'this' and its block
A 'this' block is simply an expression followed by a code block. It is used to focus the attention of a block of code on one specific value (referred to by this). Within the block, multiple actions may be performed using that value.
# Calculate the value of 'this' (0.174533) Float.Pi/180 # 'this' block that makes use of calculated 'this' value # to convert from degrees to radians quarter = 90 * this acute = 10 * this
'this' blocks can be nested within each other. For any statement, the value of this is established by the inner-most 'this' or method block it lies within. Every method block sets the value of this to be equivalent to self. Control blocks have no impact on the value of this.
Although a 'this' block can focus on any value, it is most often a newly created or existing collection.
'this' operators
A number of Acorn's operators are designed to work with this by default. For example, all of the following operators assume that 'this' is implied on the left when the operator is used as a prefix (rather than its typical infix role):
- .
- Method call or property access. (.size is equivalent to this.size)
- .:
- Property access. (.:size is equivalent to this.:size)
- ::
- Indexed access using a literal symbol. (::size is equivalent to this::size)
- <<
- Append, placing the value at the end of an ordered collection. (<<4.7 is equivalent to this<<4.7)
- >>
- Prepend, inserting the value at the start of an ordered collection. (>>event is equivalent to this>>event)
Additionally, the ':' and ':=' infix operators implicitly work with 'this':
color: 'brown' # equivalent to: this.color = 'brown' action:= [x] # equivalent to: this::action = [x] {x*x} x*x
List example
Ordered lists are easily populated:
# Create an ordered list letters = +List << 'b' << 'c','d','e' # all comma-separated or return values are appended >> 'a' # letters has the values: 'a', 'b', 'c', 'd', 'e'
The 'using' variation of the 'this' block may be used to automatically append the value of every indented statement (using <<) to 'this':
+List using '<<' 'a' 'b' 'c', 'd', 'e'
Additionally, the 'using' clause may specify a method to use on 'this' for every indented line's value:
list = +List using [x] {<< x*x} 1;2;3 list # +List(1,4,9)
Text example
Textual content may be built similarly to Lists:
Vm using 'Print' "The answer is ";answer;".\n"
Index example
The ':=' operator makes it easy to populate an Index with keyed values:
toDutch = +Index girl:= 'meisje' horse:= 'paard' dog:= 'hond'
We cannot use the ':' operator to populate an Index, since the ':' operator sets properties and an Index's keys are not properties. As with ':', when ':=' immediately follows a name token, it is treated as a symbol. To ensure a name is treated as a variable, simply enclose it in parentheses.
Type example
Types are built similarly to Index collections. See the Type Definition chapter for examples.
Iteration and 'each'
Iterators
An iterator is a stateful method (closure or yielder) which can be used to retrieve values one at a time from a sequence of values. Since we want an iterator to be able to tell us when it is done, an iterator returns two values (as a general convention): the first is null when done and the second is the desired value.
For example:
counter =[] return null, null if count>=2 count = count + 1 true, count counter() # true, 1 counter() # true, 2 counter() # null, null
Iterators are used "lazily". They only retrieve one value at a time when requested and do not know what the next value will be until asked to get it.
Typically, an iterator is single-use: it is created just before it is needed, used to retrieve values in a single pass, and then discarded afterward as its state makes it now useless to us, given that it will only ever afterward return null, null.
'Each' method
Several of Acorn's core types (such as Range, Text, List, Index and Type) provide a method called 'Each'. When applied to an instance of these types, the 'Each' method creates and returns an iterator (typically as a closure). This iterator can then be used to retrieve one value at a time from the sequence of values defined by that instance.
For example:
counter = +Range(1,2).Each counter() # true, 1 # Text iterator will return each character chars = "abc".Each chars() # 1, "a" mbrs = +List('abc', 4) mbrs() # 1, 'abc' mbrs = +Index {key: 'value'} mbrs() # 'key', 'value'
Please notice the first value returned by these iterators: it is the (never null) index of that value within the instance's sequence. Additionally, please notice that if an instance's sequence of values are ordered, the iterator retrieves them in order. If not (as is true for an Index), the iterator may well return the values in a different order each time.
'each' blocks
An 'each' block is like a 'while' block. They both repetitively perform a block of code. However, an 'each' block is given an iterator rather than a condition. It executes that block of code for each value it retrieves from that iterator. It stops when the iterator is done.
For example:
# Builds a list containing 1, 4, 9, 16 +List each x in +Range(1,4).Each << x*x
Every 'each' block specifies three things:
- The iterator which obtains each value (specified after 'in')
- The name(s) of the local variable(s) that hold the current value(s) retrieved by the iterator (in this case x). Lexically, these variable names are treated as if declared locally within the block. Thus their value changes each iteration and they are not accessible lexically outside this block.
- What actions to perform on each iterated value (in this case, square it: x*x and append that value to 'this').
'break' and 'continue' statements work inside 'each' blocks the same way they do in 'while' blocks.
As you can see, 'each' blocks, working in conjunction with corresponding 'this' blocks, provide a simple but powerful mechanism for building procedurally-generated collections (essentially accomplishing the work of a comprehension). 'each' blocks can also be very effective tools for analyzing, summarizing or transforming complex content.
The index value You may have noticed that even though iterators return two values, the sample code only demonstrated access to the second value. The 'each' actually does look at the first value under the covers, as it stops performing the block of code as soon as the iterator returns a first value of 'null'.
If you want your 'each' block's code to see the first value returned by the iterator (since it is often the collection's index for the iterator's retrieved value), just ask for it using two variable names separated by a colon:
# this code is equivalent to: found = dragon('color') found = null each key:value in dragon found = value if key='color'
Some iterators may return more than two values. Specify additional variable names separated by commas to access the remaining returned values.
Implicit iterator creation
The 'each' block attempts to make an iterator out of any value you give it:
- Closure or Yielder context. A closure or yielder context is assumed to already be the desired iterator, so 'each' uses this value directly.
- Method or Yielder method. The method is called. The value it returns is presumed to be a usable iterator.
- Collection or Range. The .Each method is called on that value to obtain the needed iterator.
Thus, we can omit specifying .Each for ranges and collections:
+List each x in 1 .. 4 # since 1 .. 4 means the same as +Range(1,4) << x*x
Similarly, 'each' easily handles the creation, initialization and use of a yielder context:
fibonacci = *[start=1, inc=0] local i = 1 while true yield i, start start, inc, i = start+inc, start, i+1 # Prints: 1: 2, 2: 6, 3: 8, 4: 14, 5: 22, 6: 36, 7: 58, 8: 94, 9: 152 each i:n in fibonacci(2, 4) Vm using 'Print' i.Text; ": "; n.Text; break if n>100 Vm.Print(", ") Vm.Print("\n")
This intelligence makes the code more concise for the common scenario of iterating through a collection. However, it also improves flexibility, as it allows one to use other methods than 'Each' for iterating through a collection. For example, a collection might offer the 'ReverseEach' method that returns a closure which iterates through that ordered collection in reverse order. Specifying each x in list.ReverseEach would make use of that iterator.
Splat (...) The 'each' block also allows you to specify '...' instead of an iterator. This will generate an iterator that provides sequential access to the ordered variable parameter values referred to by '...'. For example, the following code aggregates the variable parameters into a List (should one want random access to its values)
+List << val each val in ...
'each' clause
Much like 'while', an 'each' block may be specified using a clause that follows a statement:
# Produces the list: 1, 9, 16 +List << x*x if x!=2 each x in 1 .. 4
Notice we included an 'if' clause as well, to filter out specific values returned by the iterator. A statement may be given multiple clauses. They are evaluated in reverse order. Thus, the above code which uses two clauses is equivalent to:
+List each x in 1 .. 4 if x!=2 << x*x
The following example demonstrates use of nested 'each' blocks, both expressed as clauses:
pairs = +List << +List(x, y) for y in 3 .. 6 for x in 1 .. 6
Finally, the following more-complex example defines an inefficient method that builds and returns a calculated list of prime numbers. Notice that it also makes use of nested 'each' blocks, where the values iterated over in the inner block depend on the values iterated by the outer block.
calcPrimes = [upto] mults = +List +List each i in 2 .. upto if !mults.Find(i) << i if i<=upto.Sqrt mults<<j each j in +Range(2*i, upto, i)
Iteration mediation: zips, joins and pipes
There are times one needs an iterator that obtains its values from multiple iterator sources. This can be accomplished by creating a closure or yielder that coordinates the handling of values retrieved from multiple iterator sources.
Zip For example, the language Haskell provides a zip comprehension capability. Here is code for an Acorn equivalent, implemented using a closure iterator:
# Haskell code: b = [(x,y) | (x,y) <- zip [1..5] [3..5]] zipper = <iter1 = (1 .. 5).Each, iter2 = (3 .. 5).Each>[] done1, val1 = iter1() done2, val2 = iter2() done1||done2, +List(val1,val2) +List <<i each i in zipper
This approach can be generalized to a reusable method that returns a zip iterator.
Combinatorial Join Haskell also offers a comprehension that performs a combinatorial join. This Acorn example accomplishes something similar using a yielder as the joining iterator:
# Haskell: a = [(x,y) | x <- [1..5], y <- [3..5]] joiner = +[] [iter1, iter2] i2list = +List {<<i each i in iter2} yield # the first invocation is just for set up each i in iter1 iter2 = i2list.Each # re-start the iterator each j in iter2 yield true, +List(i,j) null, null # signal that joiner iterator is done joiner((1 .. 5).Each, (3 .. 5).Each) # do the set up +List <<i each i in joiner
This algorithm too can be generalized into a Joiner method that returns an iterator. The generalized method could even accept a first-class method that specifies how to join together the values from various iterators.
Acorn is quite flexible here, allowing creation of a wide variety of complex joining schemes. It could even support providing an SQL-like query that selects records from various sources (SELECT), joins them based on specified conditions (WHERE), and even orders the results.
Finally, Acorn supports the creation of piping iterators (using closures or yielders) which filter or transform values sourced from another iterator.