spencer.christensen
Why is [1, 2, 3 | 4] a valid list if lists are linked lists?
I did not formally study Computer Science, so please bear with me if I am missing something obvious. I am studying the inner workings of functional programming concepts and wrote this simplistic map function:
defmodule EnumMap do
def map(proc, [head | []]) do
proc.(head)
end
def map(proc, [head | tail]) do
[proc.(head)] ++ map(proc, tail)
end
end
EnumMap.map(fn x -> x + 1 end, [1, 2, 3, 4])
This produced: [2, 3, 4 | 5].
I thought “that’s weird, why is 5 being explicitly called with a list constructor?”
Aha, then I caught my mistake:
defmodule EnumMap do
def map(proc, [head | []]) do
[proc.(head)] # I needed to wrap this result in a list
end
def map(proc, [head | tail]) do
[proc.(head)] ++ map(proc, tail)
end
end
EnumMap.map(fn x -> x + 1 end, [1, 2, 3, 4])
Producing: [2, 3, 4, 5]
Hooray! However, that then made me wonder!
If Elixir lists are linked lists, why is [2, 3, 4 | 5] allowed? Isn’t that explicitly saying “this is a linked list, except for the final element which is just an integer.”? Is there a benefit to having linked lists where the final element is not in fact a list? Are there certain data structures or algorithms that use these sort of odd linked lists?
Marked As Solved
derek-zhou
Because you have a subtle bug in your code, as you find out later.
[2, 3, 4 | 5] is an improper list. You should avoid it, but there is nothing in the language to forbid it.
If you are interested in improper lists, you can read: Making sense of Elixir (improper) lists
Also Liked
D4no0
sabiwara
The cons operator is indeed much more idiomatic in this case, but this is mostly a style preference, there is no performance or behavior implication.
Both will generate the same bytecode when prepending a known number of elements with ++.
spencer.christensen
Thank you for sharing that article. Super interesting read! Also made me realize my use of lists could be optimized:
My original implementation used appending to create the list:
[proc.(head)] ++ map(proc, tail)
However I could instead use the cons operator to actually form a linked list as it is intended:
[proc.(head) | map(proc, tail)]
It also was redundant in its application of [proc.(head)] and could be simplified for just matching against an empty array.
Better version:
defmodule EnumMap do
def map(_proc, []), do: []
def map(proc, [head | tail]) do
[proc.(head) | map(proc, tail)]
end
end
EnumMap.map(fn x -> x + 1 end, [1, 2, 3, 4])
Last Post!
sabiwara
Indeed, this is exactly what tail recursion is!
Your code is exactly how you would write it.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #hex









