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