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.