Whats the purpose of using the "Head and tail" Syntax?

Hello,

Learning about the Lists and I came across pattern matching using the Head and Tail Syntax. [Head | Tail] = [1, 2, 3, 4, 5]. is this similar to arrays in JS? What would I need to use this for if its suppose to be immutable?

2 Likes

It is very much not similar to arrays in most languages, as arrays can usually access any element in the same amount of time.

Lists are linked lists, so accessing the Nth element requires looking at the first N-1. [1,2,3,4,5] is a shorthand way of writing [ 1 | [ 2 | [ 3 | [ 4 | [ 5|[]]]]]]

The Erlang manual has a short discussion

A very common idiom in Elixir is pattern-matching on the head and then recursing with the tail, for instance:

def double([]), do: []
def double([head | tail]) do
  [2 * head | double(tail)]
end

Note that writing this code in this way in some languages would be a performance problem, since double would need a lot of stack space for intermediate results. Erlang’s VM knows how to optimize this sort of pattern into a memory-efficient instruction sequence, so you may not have to worry about tail-recursion.

7 Likes

Lists in Elixir are linked lists which come with some tradeoffs. For example, prepending to a linked list is fast whereas appending is slow.

Regarding the ability to pattern matching the head and tail of a list, it comes in handy especially with recursion.

defmodule Math do
  def sum_list([head | tail], accumulator) do
    sum_list(tail, head + accumulator)
  end

  def sum_list([], accumulator) do
    accumulator
  end
end

IO.puts Math.sum_list([1, 2, 3], 0) #=> 6

source: https://elixir-lang.org/getting-started/recursion.html#reduce-and-map-algorithms

Edited to add:

Just wanted to highlight that the sum_list example above intentionally includes an additional accumulator parameter to optimize for tail recursion because there are optimizations when the result is not a list as noted in the erlang docs link shared by @al2o3cr.

4 Likes

Just to throw a tiny bit more stuff at you to maybe help understand its power, you aren’t limited to matching just head and tail, you can do things like:

[a, b | rest] = [1, 2, 3, 4, 5]
a #=> 1
b #=> 2
rest #=> [3, 4, 5]

I won’t throw more code at you right now but, for example, you could use that to write a function that iterates over each neighbouring pair in a list (1,2, 2,3, 3,4, and 4,5).

7 Likes

The first week or two of Advent of Code is great practice for fundamentals. Day 5 is good for lists.