Just like how enumerables implement a special protocol that allows them to be iterated over, there's a collectable protocol that some data structures implement that allows values to be inserted into them.
The Enum.into/2
and Stream.into/2
functions take advantage of this collectable protocol to insert elements into a collectable.
Here's an example.
iex> Enum.into(1..10, [])
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
iex> Enum.into(1..10, [5, 4, 3])
[5, 4, 3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
iex> Enum.into(1..10, %{})
** (ArgumentError) argument error
(stdlib) :maps.from_list([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
(elixir) lib/map.ex:182: Map.new_from_enum/1
iex> Enum.into([{:name, "Bob"}, {:age, 43}], %{})
%{age: 43, name: "Bob"}
Lists are collectables, so I was able to insert the range 1 to 10 into an empty list. If I use a non-empty list, the range is inserted at the end of the list.
Maps are also collectables, but we can't insert numbers into a map. Maps need key-value pairs, and I was able to successfully insert key-value tuples into a map. By the way, I had no idea this was possible until I wondered about it and thought I'd try it out. I figured that if maps are enumerable (they can emit key-value pairs) when they are also probably collectable. It can sometimes pay off to play around and try things out.
I still haven't learned anything about protocols other than knowing there are enumerable and collectable protocols, but they're increasingly looking like interfaces. In fact, I have a vague recollection that the Swift and Objective-C languages (both of which I learned many years ago, but never used) may also have something called a protocol that's an interface.
So when you see a "collectable" being mentioned, be aware that it indicates any data structure with standard functionality to have values added into it.