4

Hi I am a newbie in the Erlang world. When I think of how we need to solve the following problem (and there are a long list of similar ones), I think it's really inefficient because we are speaking of a lot of recursion. Apprently, language like C/Java would not need the clumsy recursion to solve this problem, but with Erlang (I guess other functional programming language needs to as well, maybe?) you must do in such a way.


Example 3 - Append

This program concatenates two lists:

append([], List) -> List;

append([First|Rest], List) -> [First | append(Rest,List)].

Can anyone give an explanation why this is not a problem ?

Zed
  • 57,028
  • 9
  • 76
  • 100
  • 1
    Clumsy recursion? If you think recursion is clumsy then you probably should not learn functional programming. Regular programming might be a bit too hard too. – freiksenet Sep 30 '09 at 05:32
  • 1
    You are being provocative in the way you ask this question. That just feed language wars. – Christian Sep 30 '09 at 10:22
  • I realize that the `append` function isn't central to your argument (which I believe has been thoroughly corrected in the answers below) but, in Erlang, you would not write this function... Instead you simply write `List1 ++ List2`. – rfunduk Oct 10 '09 at 19:04

5 Answers5

12

First of all, read the Erlang efficiency guide on recursions.

As for the clumsiness, don't forget that Erlang lists are single-linked lists, so you only have a "pointer" to the head of the list, and need to access elements by traversing the list. This would require the same amount, but different kind of clumsiness from those languages with all the pointer or reference juggling.

As for efficiency, you can implement it in a tail recursive fashion. Tail recursion is optimized (see this SO question) in a way that the compiled code becomes similar to what you implement in C++, only difference is that instead of the code pointer jumping around, the stack pointer is rewind, etc.

Anyway, try to implement the very same functionality in Java and C++ and then we will see which one is clumsier and more readable.

Community
  • 1
  • 1
Zed
  • 57,028
  • 9
  • 76
  • 100
7

Hi I am a newbie in the C world. When I think of how we need to solve the following problem (and there are a long list of similar ones), I think it's really inefficient because we are speaking of a lot of looping. Apprently, language like Erlang would not need the clumsy looping to solve this problem, but with C (I guess other procedural programming language needs to as well, maybe?) you must do in such a way.

See what I did there? ;)

As stated by others, recursion is a perfectly normal (and efficient) way to solve things in many languages. Not directly related, but... for some things, recursion is considerably clearer to understand than looping would be (the reverse is, of course, also true)... fib(n).

RHSeeger
  • 16,034
  • 7
  • 51
  • 41
4

Well, why do you think recursion is inefficient?

In Erlang's case it uses tail recursion (.Net IL can do this as well), which is actually slightly more efficient in terms of processing.

For example, with tail recursion, the CPU does something like this:

var first_list, second_list

label START_ITER:
  if(first_list is empty)
    goto FINISH

  var first_elem = first_list[0]
  var first_list.start_element = first_list[1]

  second_list[n+1] first_elem
  goto START_ITER

label FINISH:
  return second_list

Whereas with a for loop, you get something like this:

var first_list, second_list

var i = 0
var limit = first_list.length

label START_ITER:
  if(i == limit)
    goto FINISH

  second_list[n+i+1] = first_list[i]
  i += 1
  goto START_ITER

label FINISH:
  // Done

The thing to note with the tail recursive example is that the list splitting just changes the node in the list that first_list points to. The for loop example uses more variable assignments to do much the same thing.

This also illustrates an important point with the differences between tail recursion (which is just a goto) and normal recursion, which will actually load up a function onto the stack. This just does not happen with tail recursion, and in erlang in general.

It's a close call, but there are some extra checks and additions in a for loop, so I can't recommend one over the other in terms of efficiency, but it terms of style, recursion has it down perfectly.

I believe it was best put as "To Iterate is Human, to recurse, divine"

Khanzor
  • 4,830
  • 3
  • 25
  • 41
0

Tail recursion is optimized in every functional language -- it has no overhead compared to a procedural language's loop (or, ahem, goto;-). While the specific example you give might or might not be "tail recursion" depending on the language (lazy vs eager functional languages, for examples), when you're coding in one specific language you can generally refactor to a purely tail-recursive expression... if and when that's needed.

In a lazy language, e.g. Haskell, the append in (the Haskell's equivalent of) [First | append(Rest, List)] would be just saved as a "thunk" to be execute when and IF needed, as opposed to eagerly expanded at once. Then, when, later, somebody pattern matches THAT thunk against a head/tail structure -- then, and only then, the thunk is first executed.

Eager (non-lazy) languages have a different approach, but it's not any less efficient (although lazy-language fans might argue it IS less elegant;-).

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 2
    Erlang does not have this neat feature of Haskell (yet? :)) Also, the example given is not tail-recursive. – Zed Sep 30 '09 at 05:41
  • One qualifier re: "every functional language" - Clojure is functional and doesn't do tail call optimization - though that's because of lack of support for it in the JVM (which JVM language developers have apparently been lobbying to get added.) Clojure has a nice "recur" form, though. – Anon Sep 30 '09 at 06:23
  • Common Lisp, Clojure and Scala are all functional languages that do not do/require TCO. – J D Nov 08 '11 at 09:13
  • TCO can have a performance overhead compared to `goto` because it can require the stack frame to be reordered. Note that TCO is an optimization in space and not time. – J D Nov 08 '11 at 09:14
0

You might not know about "erlc -S" and "to_core" options, which lets you inspect the bytecodes (BEAM or HiPE)

pattern matching - implementation

http://www.nabble.com/Re%3A-Idiomatic-Erlang%2C-style-performance-question-p18061921.html

HTH

Community
  • 1
  • 1
Gene T
  • 5,156
  • 1
  • 24
  • 24