[head | tail] when tail is not an array?

I’m a bit confused about the | operator with an array. From what I understand, the tail is supposed to be an array. E.g. [1, 2 | [3]] correctly returns [1, 2, 3]. But i noticed that if you mistakenly do [1, 2 | 3], there’s no error. Doing any enumeration functions on it will then fail, e.g doing Enum.member/2 or List.last/1. Does [1, 2 |3] actually have any meaning? Or is it just an error that doesn’t cause an immediate exception?

:wave:

It’s called an improper list and is useful in IO, usually used to build iolists ["like " | "this"].

Might be somewhat relevant:
https://www.evanmiller.org/elixir-ram-and-the-template-of-doom.html
https://www.bignerdranch.com/blog/elixir-and-io-lists-part-1-building-output-efficiently/

1 Like

Here’s a quickie description:

Lists are implemented as Cons Cells, which are quite literally just 2-tuples underneath, so you can think of [1 | 2] as being like {1, 2}. The empty list (known properly as nil, which is not Elixir’s nil, it’s nil is wrong, lol) is [], which is like the empty tuple of {}.

Now with that in mind, the BEAM pretty-prints a Cons Cell that has another Cons Cell in its tail by just taking the internal print of the right Cons Cell and separating it with a ,, I.E. instead of printing it as [1 | [2 | []]] it just pretty prints it as [1, 2] instead.

Thus you can put anything you want into both the head and tail of a Cons Cell, and a [... | ...] is a Cons Cell. If you have a Cons Cell that just happens to have another Cons Cell in it’s tail, it pretty prints it better, but otherwise it still works fine as normal.

That’s it. And that is about how single-linked lists are implemented in about any language. :slight_smile:

7 Likes

Thanks for the explanation!

1 Like