Hi Clojurists,
I’m coming from Elixir that was built on top of Erlang where something like this is OK:
defmodule MyApp do
def sum_numbers([head | tail], accumulator),
do: sum_numbers(tail, head + accumulator)
def sum_numbers([], accumulator),
do: accumulator
end
IO.puts(MyApp.sum_numbers([1, 2, 3, 4, 5], 0))
# => 15
Explanation:
When the collection has at least a single element the first function splits the collection to first
and rest
. If the collection is empty then the second functions is executed instead and it simply returns the accumulated number (sum).
Learning Clojure I see that recur
has two reasons to exist. First is that if you rename the function, you don’t need to rename the recursive call. Second is that it prevents StackOverflow since Java is not Erlang and stack has a limit.
And loop
is to a) make it simple by not exposing (in case of the previous Elixir function) the accumulator and b) no need to write two function arities.
In Elixir loop
would look like this:
defmodule MyApp2 do
def sum_numbers(list),
do: sum_numbers(list, 0)
defp sum_numbers([head | tail], accumulator),
do: sum_numbers(tail, head + accumulator)
defp sum_numbers([], accumulator),
do: accumulator
end
IO.puts(MyApp2.sum_numbers([1, 2, 3, 4, 5, 6]))
# => 21
and of course the Clojure implementation would be
(defn sum-numbers [seq]
(loop [seq seq sum 0]
(if (empty? seq)
sum
(recur (rest seq) (+ sum (first seq))))))
(sum-numbers [1 2 3 4 5])
;; => 15
or
(defn sum-numbers2 [seq]
(loop [seq seq sum 0]
(if-let [tail (next seq)]
(recur tail (+ sum (first seq)))
(+ sum (first seq)))))
(sum-numbers2 [1 2 3 4 5 6])
;; => 21
One of the reasons I’m asking is that based on watching Rich Hickey’s talks, it seems that some parts of Clojure were shaped to push a developer into right direction so he/she uses the language properly. And I’m thinking if there is some hidden meaning behind loop
and recur
to push people to the right direction or some hidden use case that I don’t know about.
Btw. thank you all for Clojuverse, really great tool and content and a lot of great reading and great people.
Leonardo
P.S. Something that can’t be done in Clojure is multi-function recursive call.
Instead of just calling itself
fn-abc ------┐
^ |
└----------┘
in Elixir you can add another function into the game
fn-abc ------┐
^ v
| fn-def
| |
| v
└----------┘
The example bellow is stupid, I couldn’t think of a good example, but I remember that I’ve seen good use cases for it.
defmodule MyApp3 do
def sum_numbers(list),
do: sum_numbers(list, 0)
defp let_me_know(list, accumulator) do
IO.puts("VERBOSE: sum: #{accumulator}")
sum_numbers(list, accumulator)
end
defp sum_numbers([head | tail], accumulator),
do: let_me_know(tail, head + accumulator)
defp sum_numbers([], accumulator),
do: accumulator
end
IO.puts(MyApp3.sum_numbers([1, 2, 3, 4, 5, 6, 7]))
# VERBOSE: sum: 1
# VERBOSE: sum: 3
# VERBOSE: sum: 6
# VERBOSE: sum: 10
# VERBOSE: sum: 15
# VERBOSE: sum: 21
# VERBOSE: sum: 28
# => 28