4

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!

sunny-mittal
  • 499
  • 5
  • 12
  • Possible duplicate of [Functional Programming: what is an "improper list"?](http://stackoverflow.com/questions/1919097/functional-programming-what-is-an-improper-list) – Gazler Jan 14 '16 at 08:29

2 Answers2

10

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.

Patrick Oscity
  • 53,604
  • 17
  • 144
  • 168
  • Such a great and thorough answer, thank you! I'm a bit confused as to why "the tail is in this case not a list, but a function..." I understand the reasoning behind it, but my example of `[ 1 | 2 ]` clearly does not have `2` as a function. Does Elixir simply assume that it 'may' lead to future values and therefore doesn't prohibit the syntax? (I'm new to Elixir and have very very little experience with Erlang, so forgive me if I'm missing something very obvious). – sunny-mittal Jan 14 '16 at 08:57
  • In your case the tail is an integer, I meant in the lazy list example it's a function. You can put anything into the tail, the lazy list is just one possible example what you can do with this – Patrick Oscity Jan 14 '16 at 10:02
3

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}.

Paweł Obrok
  • 22,568
  • 8
  • 74
  • 70