Working with multiple values#
Lists, records, and other ways of combining multiple values are a convenience, but sometimes we want to treat all of these values uniformly and do things with them.
For most cases, Crochet’s standard library for collections provides a set of commands for different needs here: things like transforming, filtering, or sorting collections. But Crochet also has some lower level syntaxes for this that can be useful from time to time.
The for .. in .. do ..
syntax#
If you’re familiar with other programming languages, you might
have seen things like “iterators” or “list comprehensions”.
Crochet has a similar idea in its for .. in .. do ..
syntax. It allows one to do something for each item of
a collection.
For example, consider the case where we have a list of characters, and we want to show which book they appear in. We could express this program as follows:
let Characters = [
[name -> "Alice", appears-in -> "Alice in Wonderland"],
[name -> "Dorothy", appears-in -> "The Wizard of Oz"],
];
for Character in Characters do
show: "[Character.name] appears in [Character.appears-in].";
end
This could output:
Alice appears in Alice in Wonderland.
Dorothy appears in The Wizard of Oz.
Here, for each item in the Characters
collection, we will
execute this little program between the do
and end
words.
And each time we execute this program, the name Character
will
be associated with one of the items in the collection. For lists,
the default behaviour is to go through the items in the order they
appear in the list.
Filtering items#
When using the for
syntax, we can also specify which items we
don’t want to do anything with, directly in the syntax itself. This
avoids having to use a condition within the program, and often makes
things easier to read.
For example, consider the case where we’re showing the items in a player inventory, but we only want to show the key story items—not regular, consumable items the player could’ve got from one of the shops along their way. We could achieve this with the following piece of code:
let Inventory = [
new key-item(blue-booch),
new consumable(potion, 10),
new equipment(red-coat),
new key-item(letter-from-alice),
];
show: "You're carrying:";
for Item in Inventory if Item is key-item do
show: "A [Item name].";
end
This could output:
You’re carrying:
A blue brooch.
A letter from Alice.
In this case, we’ll still go through all of the items in the inventory,
but we’ll only execute the body of the for
syntax if the item we’re
looking at is of the type key-item
. So we execute the body twice.
Once for blue-brooch
and once for letter-from-alice
.
Combining collections#
Sometimes we have several collections that we want to consider when doing something. For example, let’s say we have a list of keys and a list of locked objects. The game wants to give the player a hint that shows which keys can open which objects, but some keys can open multiple things.
Here, what we really want is to go through every key, and then
for each locked object, verify if we can use that key to open it.
This can be achieved in Crochet by using multiple .. in ..
clauses in the for syntax:
let Keys = [red-key, golden-key, blue-key];
let Objects = [music-box, desk-drawer, jewelry-box];
for Key in Keys, Object in Objects if Key opens: Object do
show: "[Key] can open [Object].";
end
If the red-key the music box, the blue-key opens the desk drawer, and the golden-key opens anything, then this could output:
The red key can open the music box.
The golden key can open the music box.
The golden key can open the desk drawer.
The golden key can open the jewelry box.
The blue key can open the desk drawer.
We could also nest these for
syntaxes for similar effect:
for Key in Keys do
for Object in Objects if Key opens: Object do
show: "[Key] can open [Object]"
end
end
This has the same observable behaviour, but the resulting value of this for expressions is slightly different.
Results of for
syntaxes#
The for
syntax isn’t only used for doing things. It can also
be used to transform and filter a collection, yielding a new
collection as a result.
For example, if we have a list of numbers, like this:
let Numbers = [1, 2, 3, 4, 5];
Then we can get the list of these numbers doubled using the for
syntax:
let Doubled = for Number in Numbers do Number * 2 end;
// Equivalent to:
[
1 * 2,
2 * 2,
3 * 2,
4 * 2,
5 * 2
];
We can also filter the list, keeping only the even numbers, using the
for
syntax:
let Even = for Number in Numbers if Number is-divisible-by: 2 do Number end;
// Equivalent to:
[2, 4]
And here’s why this makes the choice of using multiple .. in ..
clauses
in the for syntax a bit different from nesting them. Imagine we have a list
of characters, and then a list of locations. We want all combinations of
characters and locations, so we combine these lists with the for
syntax:
let Characters = ["Alice", "Dorothy"];
let Locations = ["Hall", "Dinning Room", "Kitchen"];
let Combined = for Character in Characters, Location in Locations do
"[Character] at the [Location]"
end;
By writing the program like this, combined will have the following value:
[
"Alice at the Hall",
"Alice at the Dinning Room",
"Alice at the Kitchen",
"Dorothy at the Hall",
"Dorothy at the Dinning Room",
"Dorothy at the Kitchen",
];
But if we nest it, like so:
for Character in Characters do
for Location in Locations do
"[Character] in the [Location]";
end
end
Then we end up with a slightly different list:
[
[
"Alice at the Hall",
"Alice at the Dinning Room",
"Alice at the Kitchen",
],
[
"Dorothy at the Hall",
"Dorothy at the Dinning Room",
"Dorothy at the Kitchen",
],
];
So, in the first case, we get a flat list containing all of the combinations. In the second case, we get a nested list containing the combinations. The result more or less follows the form we choose to write the program.