Elixir iolists
Elixir has a few different ways of representing lists of things. Each has it’s own behaviour, performance characteristics, and weird quirks.
There is a great overview of binaries, strings, and charlists in the docs.
Binaries
Just a series of bytes in memory. With a size (no buffer overflows)
iex> <<1,2,3,4>>
<<1,2,3,4>>
Exactly what it says on the tin.
Strings
Also binaries, but with just printable characters. If a String has an unprintable character, it’s not a String, it’s a binary.
iex> string = "hello"
"hello"
iex> <<"hello">>
"hello"
iex> <<104,101,108,108,111>>
"hello"
iex> string == binary_string
true
iex> string == binary_bytes
true
Also does unicode.
iex> string = "hełło"
"hełło"
Unicode codepoints may be more than one byte.
iex> binary_bytes = <<104,101,197,130,197,130,111>>
"hełło"
Lists
Elixir lists are linked list. “cons lists”, where the head contains a value, and a reference to the tail of the list. This goes on recursively until the last element, which points at an empty list.
iex> list = [1,2,3,4]
[1,2,3,4]
iex> cons_list = [1 | [2 | [3 | [4 | [] ] ] ] ]
[1,2,3,4]
This is constructed in memory as (1) -> (2) -> (3) -> (4) -> nothing!
Because Elixir is immutable, the only way to construct a list is to prepend a new element pointing at the existing tail. This is fast and can be done in constant time.
Appending is slow, as it means you have to traverse the entire list to find the
end, and create a new node for each element in memory pointing at the new tail.
Generally you don’t want to do this. It’s faster to prepend to a list, then
reverse once at the end with :lists.reverse/1
- which is implemented in native C
and is fast
iex> list = [1]
iex> list = [2 | list]
iex> list = [3 | list]
iex> list = [4 | list]
[4,3,2,1]
iex> :lists.reverse(list)
[1,2,3,4]
Charlists
Also lists, but contain only integers which are codepoints for printable ASCII characters. If it the characters aren’t printable, then it’s just a plain old list of integers.
Charlists are denoted by single quotes (regular strings are double quotes)
iex> 'hello'
'hello'
They don’t really do unicode very well.
iex> 'hełło'
[104, 101, 322, 322, 111]
Erlang uses charlists for String representation instead of binaries like Elixir.
You don’t often have to use charlists in Elixir. Usually it’s because you’re integrating with an Erlang library.
iolists
Any list containing integers, binaries, or other lists.
Allows you to construct lists in order and nested.
Each element is wrapped in a new list with the old. Useful for building up large binaries on the fly of different data types.
iex> l = ?f
iex> l = [l, "oo"]
iex> l = [l, [?b, ?a]]
iex> l = [l, "rbaz"]
[[[102, "oo"], 'ba'], "rbaz"]
This is not useful on it’s own but can be passed to IO
functions directly.
This is ideal for when you want to eventually write binary data to disk or
network etc. Your code can build up an iolist performantly without copying
around data to append to existing binaries.
iex> IO.iodata_to_binary l
<<1,2,3,4>>
This is what Phoenix etc uses internally for templates.
e.g.
<h1>Welcome to my website <%=@name%>!</h1>
The template is just a function. The result that is returned from the function would look something like:
[["<h1>Welcome to my website ", "madlep"], "!</h1>"]
iodata
Just a union type which can be a binary OR an iolist. You usually see iodata being referred to in the docs rather than iolist