This version of the for construct loops over an E collection, matching both the key-pattern and the value-pattern against successive key-value pairs from the collection. Variables defined in either pattern are available to the body-expr, which is evaluated once for each matching key-value pair. Again, in common usage the key-pattern and value-pattern can be just variable names, which will get bound to each of the key-value pairs in the entire collection. ? pragma.syntax("0.8") ? for key => value in [1, 3, 5, 100, 6] { > println(`$key maps to $value`) > } # stdout: 0 maps to 1 # 1 maps to 3 # 2 maps to 5 # 3 maps to 100 # 4 maps to 6 # As we can see, the keys of a list are the indices into the list. More interestingly, E's map collections are hash tables with arbitrary keys and values. A map can also be constructed with square brackets, but instead of writing single elements we write key-value pairs separated by =>. As a silly example, let's suppose we want to concatenate together all the names of capital cities of states whose names include the letter "e": ? def capitals := [ > "Pennsylvania" => "Philadelphia", > "New York" => "Albany", > "California" => "Sacramento" > ] # value: ["Pennsylvania" => "Philadelphia", \ # "New York" => "Albany", \ # "California" => "Sacramento"] ? capitals["New York"] # value: "Albany" ? var result := "" # value: "" ? for state => city in capitals { > if (state.includes("e")) { > result += city + " " > } > } ? result # value: "Philadelphia Albany " But since the body of the loop is only executed if both pattern matches succeed, we can move tests up into these patterns. Since the above loop makes no other use of the "state" variable, it can be rewritten this way: ? var result := "" # value: "" ? for `@{_}e@_` => city in capitals { > result += city + " " > } The key-pattern in this last example will only match strings containing the letter "e". Custom CollectionsBesides conventional collection classes (mutable and immutable arrays and hashtables), a variety of other objects can also be treated as collections of key-value pairs, and can be iterated over with a for loop. In the Jabberwocky example, we saw that E can treat a normal text file (as well as a URL object) as a collection of line-number => line pairs, and a directory as a collection of file-name => file pairs. How can the E programmer make new kinds of objects over which the for loop can iterate? When the for loop iterates over a collection, it calls its iterate(assocFunc) method, passing in as assocFunc a function of two arguments. To make new objects work with for, we need to define an iterate method that applies this assocFunc to all the things we wish to appear as key-value pairs in the collection. So, for example, let's say we get tired of using recursion to get to all the files at the leaves of a directory tree. Rather, we'd like to create an object that acts like a simple collection of these leaves. We can accomplish this with the following function: ? def leaves(filedir) :any { > def leafCollection { > to iterate(assocFunc) :void { > if (filedir.isDirectory()) { > for sub in filedir { > leaves(sub).iterate(assocFunc) > } > } else { > assocFunc(filedir.getName(), filedir) > } > } > } > } # value: <leaves> Now leaves(filedir) defines an object leafCollection that acts like a collection of the leaves of the directory tree rooted at filedir. Since this def is the last thing to be evaluated in leaves, this object is returned. Our definition of its iterate(assocFunc) method makes this collection suitable for use in a for loop. For example, I got curious about how many Java files I had in a particular directory tree: ? var count := 0 # value: 0 ? for `@_.java` => _ in leaves(<file:~/e/src>) { > count += 1 > } ? count # example value: 942 |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Unless stated otherwise, all text on this page which is either unattributed or by Mark S. Miller is hereby placed in the public domain.
|