0

How to do nil safe concatenation of lists in Elixir?

I have presented few inline examples of the situation and response with ++

Looks like nil is treated as a special entity when concatenating.

Why does this happen?

# Append two empty list ✅
iex(1)> [] ++ []
[]
# Append empty list to nil ❌
iex(2)> [] ++ nil
nil # Result should be []
# Append non empty list to nil ❌
iex(3)> [1] ++ nil
[1 | nil] # Result should be [1]
# Append non empty list to nil and enumerate ❌
iex(5)> a = [1, 2] ++ nil
[1, 2 | nil]
iex(6)> Enum.each(a, fn p -> IO.puts(p) end)
1
2
** (FunctionClauseError) no function clause matching in Enum."-each/2-lists^foreach/1-0-"/2
# Add more lists with nil ❌
iex(5)> [1, 2] ++ nil ++ []
** (ArgumentError) argument error
Adam Millerchip
  • 20,844
  • 5
  • 51
  • 74
Mrinal Saurabh
  • 948
  • 11
  • 16

3 Answers3

5

I believe you want to get back a normal, proper list. For that both arguments in a call to ++/2 must be lists.

You are probably after List.wrap/1 for nil.

iex|1 ▸ [] ++ List.wrap([])
#⇒ []
iex|2 ▸ [] ++ List.wrap(nil)
#⇒ []
iex|3 ▸ [1] ++ List.wrap(nil)
#⇒ [1]
iex|4 ▸ [1, 2] ++ List.wrap(nil)
#⇒ [1, 2]
iex|5 ▸ [1, 2] ++ List.wrap(nil) ++ [3]
#⇒ [1, 2, 3]
iex|6 ▸ [1] ++ List.wrap(2)            
#⇒ [1, 2]
Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
4

You have discovered improper lists: a list where the tail of the last element is not an empy list. There's a nice example in the List documentation:

Regular list (equivalent to [1, 2, 3]):

[1 | [2 | [3 | []]]]

Improper list (equivalent to [1, 2 | 3]):

[1 | [2 | 3]]

There's nothing special about nil - the same thing happens for any non-list:

iex(1)> [1, 2] ++ "foo"
[1, 2 | "foo"]
iex(2)> [1, 2] ++ %{3 => 4}
[1, 2 | %{3 => 4}]
iex(3)> Enum.each([1,2|"boom"], &IO.puts(&1))
1
2
** (FunctionClauseError) no function clause matching in Enum."-each/2-lists^foreach/1-0-"/

From the documentation for ++:

List concatenation operator. Concatenates a proper list and a term, returning a list.

If the right operand is not a proper list, it returns an improper list. If the left operand is not a proper list, it raises ArgumentError.

The error from Enum.each is strange, but it seems to be caused by the implementation of expecting a proper list, too.

I think this answers your question, but I suspect it doesn't solve your problem. You didn't mention why you are appending nil to lists, nor what you are trying to achieve.

Adam Millerchip
  • 20,844
  • 5
  • 51
  • 74
1

If you want to eliminate the nil part then you can use pattern matching for it. eg:

defmodule Example do
  def add(list1, list2) do
    case [list1, list2] do
      [nil, nil] -> []
      [nil, list2] -> list2
      [list1, nil] -> list1
      [list1, list2] -> list1 ++ list2
    end
  end
end
rathourarv
  • 126
  • 1
  • 7