4
votes

So I've been playing with Elixir and am a bit confused about something:

iex> [ 1 | [ 2 ] ] // [ 1, 2] (expected)
iex> [ 1 | 2 ] // [ 1 | 2 ] (huh?)

My confusion is in why the second version does what it does. I understand that 2 is not a list, so it can't concatenate the "head" with the "tail", but, in my opinion, it should throw an error when the tail is not a list. I've been trying to think of a use-case for having this behavior but have come empty-handed. If anyone can explain why this is the desired behavior, I'd really appreciate it. Thanks!

2

2 Answers

10
votes

The tail of a list can actually be any term, not just another list. This is sometimes called an "improper list".

The Erlang documentation gives an example on how to use this to build infinite lists, but it is unlikely that you will encounter this in the wild. The idea is that the tail is in this case not a list, but a function that will return another improper list with the next value and function:

defmodule Lazy do
  def ints_from(x) do
    fn ->
      [x | ints_from(x + 1)]
    end
  end
end

iex> ints = Lazy.ints_from(1)
#Function<0.28768957/0 in Lazy.ints_from/1>

iex> current = ints.()
[1 | #Function<0.28768957/0 in Lazy.ints_from/1>]

iex> hd(current)
1

iex> current = tl(current).()
[2 | #Function<0.28768957/0 in Lazy.ints_from/1>]

iex> hd(current)
2

iex> current = tl(current).()
[3 | #Function<0.28768957/0 in Lazy.ints_from/1>]

iex> hd(current)
3

However, we can achieve infinite streams much more easily in Elixir using the Stream module:

iex> ints = Stream.iterate(1, &(&1+1))
#Function<32.24255661/2 in Stream.unfold/2>

iex> ints |> Enum.take(5)
[1, 2, 3, 4, 5]

Another (pseudo) use case of improper lists is with so-called iodata or chardata values. These allow you to optimize situations where you need to frequently append to a charlist (single quoted string), due to the fact that charlists are linked lists for which appending is expensive. You normally don't really see improper lists with chardata in the wild either, because we can just use regular lists – but rest assured they could be used to build a chardata. If you want to learn more about chardata in general, I recommend this blog post from The Pug Automatic.

3
votes

Another way to look at it: a list is just a tuple with the first element being the head, and the second element being the tail (another list). When you write [x | y] you construct such a tuple with first element x and second element y. If y happens to be a list itself then the whole thing is a proper list. But y can be anything and in that case [x | y] is just a tuple, similar in function to {x, y}.