There are three main parts:
- Generators
- Filters
:into
for combines features of map, filter and into.
for element <- Enumerable do
element
end
#=> returns a listA generator is used to specify what we want the for macro to iterate over. This takes the format of element <- Enumerable where element is the variable that you want to assign each value in the Enumerable to.
A single generator behaves like a map:
for name <- ["Joe", "Suzy"] do
String.upcase(name)
end
#=> ["JOE", "SUZY"]
# is equivalent to:
Enum.map ["Joe", "Suzy"], fn(name) ->
String.upcase(name)
endUse multiple generators to create complex lists.
deck = for suit <- [:hearts, :diamonds, :clubs, :spades],
face <- [2, 3, 4, 5, 6, 7, 8, 9, 10, :jack, :queen, :king, :ace] do
{suit, face}
end
#=> [{:hearts, 2}, {:hearts, 3}, {:hearts, 4}, {:hearts, 5}, ...]Generators can do filtering with a pattern:
for {:spades, face} <- deck do
{:spades, face}
endYou can do more complex filtering with Filters.
for element <- Enumerable, filter do
element
endYou can use variables from the generators in filters.
for {:spades, face} <- deck, is_number(face) do
{:spades, face}
end
#=> [{:spades, 2}, {:spades, 3}, ... , {:spades, 10}]You can have multiple filters:
for {suit, face} <- deck,
suit == :spades,
is_number(face),
face > 5 do
{suit, face}
end
#=> [{:spades, 6}, {:spades, 7}, {:spades, 8}, {:spades, 9}, {:spades, 10}]This is used to make the for macro return something other than a list. For example, when filtering maps it can be used to ensure that the returned value is also a map.
for {key, val} <- %{name: "Daniel", dob: 1991, email: "..."},
key in [:name, :email],
into: %{} do
{key, val}
end
#=> %{name: "Daniel", email: "..."}You can use :into with the for macro to return anything that implements the Collectable protocol. The following types support Collectable:
- Map
- List
- IO.Stream (can use this to continuously print from STDIN to STDOUT)
- Bitstring (Binary)
Covered in more depth in a later chapter.
pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
for <<r::8, g::8, b::8 <- pixels>>, do: {r, g, b}
#=> [{213, 45, 132}, {64, 76, 32}, {76, 0, 0}, {234, 32, 15}]| | `Enum` | `Stream` | `for` |
| `map` | YES | YES | YES |
| `filter` | YES | YES | YES |
| Lazy | NO | YES | NO* |
| Iterations | DEPENDS | ONE | ONE* |
| & Operator | YES | YES | NO** |
for, whilst not lazy, does only need one iteration to run and so is more efficient thanEnum. However you cannot composeforin the same way as you can withStream. The fullfor"process" must be built in one place and cannot be added to after the fact.
** because for takes a do: block and not a function, you cannot use the capture operator with it
IMPORTANT
- When running a single operation use
Enumoffor(preferEnumunless good reason to do othereise though) - When running multiple operations use
Streamorfor(preferforunless you want to compose across multiple lines) - When generating a list use
foror aStreamgenerator (definitely preferforfirst) - When working with multiple lists use
for