79

I am getting introduced to Functional Programming [FP] (using Scala). One thing that is coming out from my initial learnings is that FPs rely heavily on recursion. And also it seems like, in pure FPs the only way to do iterative stuff is by writing recursive functions.

And because of the heavy usage of recursion seems the next thing that FPs had to worry about were StackoverflowExceptions typically due to long winding recursive calls. This was tackled by introducing some optimizations (tail recursion related optimizations in maintenance of stackframes and @tailrec annotation from Scala v2.8 onwards)

Can someone please enlighten me why recursion is so important to functional programming paradigm? Is there something in the specifications of functional programming languages which gets "violated" if we do stuff iteratively? If yes, then I am keen to know that as well.

PS: Note that I am newbie to functional programming so feel free to point me to existing resources if they explain/answer my question. Also I do understand that Scala in particular provides support for doing iterative stuff as well.

peakit
  • 28,597
  • 27
  • 63
  • 80
  • I don't know if this helps but most of the problems can be solved effectively using recursion. In most cases we find ourselves in situations wherein we have to do a same set of operations repeatedly over the result of the same operation. Problems like these come under the category of `The divide and conquer paradigm`. But I'm giving you a perception from the algorithmic point of view. I don't know why recursion is preferred over iteration to problems which can be solved by both. But in most of the divide and conquer type of problems, recursion is the only cjoice you have got . – Nithish Inpursuit Ofhappiness Sep 30 '12 at 07:49
  • @NithishInpursuitOfhappiness Recursive and iterative solutions are equivalent, you can *always* convert one into the other. –  Sep 30 '12 at 07:55
  • perhaps this might help... [recursion is the new iteration](http://blogs.msdn.com/b/ashleyf/archive/2010/02/06/recursion-is-the-new-iteration.aspx) – Nithish Inpursuit Ofhappiness Sep 30 '12 at 07:55
  • @delnan I agree. But it's easy to handle recursion than iteration in cases like `multiplying large numbers`; Complexity of some algorithms can be reduced from moving to recursion from iteration. – Nithish Inpursuit Ofhappiness Sep 30 '12 at 08:00
  • 2
    @NithishInpursuitOfhappiness The code is simpler when you rely on the built-in stack, sure, but in most cases it's pretty simple (just ugly) to replace the recursion with a loop + manual manipulation of a manually allocated stack. –  Sep 30 '12 at 08:04
  • @Delnan And also, by complexity i don't just mean the human complexity of programming. The time complexity for running the program itself can be reduced. For eg: Binary search – Nithish Inpursuit Ofhappiness Sep 30 '12 at 08:12
  • 2
    @NithishInpursuitOfhappiness How so? I'm not talking about choosing a different algorithm which does not divide and conquer, I'm talking of implementing the same algorithm using O(1) space on the language implementation's stack. The only thing you change is replacing each recursive call with pushing onto a stack/array (which is O(1)), and accordingly each access to locals with an array access (which is also O(1)). Sometimes it's even simpler and you get along with the same time complexity and O(1) space use instead of, say, O(n) or O(log n) space use. –  Sep 30 '12 at 08:46
  • @Delnan Oh i get it.. So you're talking something related to dynamic programming right? – Nithish Inpursuit Ofhappiness Sep 30 '12 at 08:54
  • 2
    @NithishInpursuitOfhappiness There is nothing special about transforming between an iterative and a recursive implementation of an algorithm. For binary search, Wikipedia has both [a recursive](http://en.wikipedia.org/wiki/Binary_search_algorithm#Recursive) and [an iterative](http://en.wikipedia.org/wiki/Binary_search_algorithm#Iterative) version. – Magnus Hoff Sep 30 '12 at 09:00
  • 1
    @NithishInpursuitOfhappiness No. I simple talk of swapping an implicit call stack for an explicit one, not altering anything about the algorithm. I'll write up an example now. As Magnus Hoff points out, in some cases (like binary search, or other tail recursive functions), there is also a more elegant iterative phrasing that doesn't even need the stack either way. –  Sep 30 '12 at 09:01
  • Just three unelaborated thoughts, perhaps someone can use it in an answer: First, recursion includes the LHS as part of the RHS in a formal statement, thus it helps in formalising an algorithm (breaking the problem down); second, perhaps when things are lazily evaluated, recursion adds the possibility to express infinite loops?; third, since recursion is about calling functions and functions can have type parameters, perhaps recursion can increase the static type safety or type specificity of an algorithm (at least that's true for recursive data structures, e.g. a finger tree)? – 0__ Sep 30 '12 at 09:07
  • @NithishInpursuitOfhappiness http://www.saasblogs.com/software-development/how-to-rewrite-standard-recursion-through-a-state-stack-amp-iteration/ has a nice example. It's not the most complicated algorithm being transformed, but it avoids further optimization which obscure how the iterative implementation was derived from the recursive one. Note that space and time complexity are exactly the same. –  Sep 30 '12 at 09:16
  • Using recursive functions instead of imperative loops is just a matter of style and things are fairly interchangeable after you get used to it. I would highly recomend the classic "Lambda, the Ultimate GOTO" paper if you want a more in-depth look at this issue. – hugomg Oct 01 '12 at 19:49

9 Answers9

54

Church Turing thesis highlights the equivalence between different computability models.

Using recursion we don't need a mutable state while solving some problem, and this make possible to specify a semantic in simpler terms. Thus solutions can be simpler, in a formal sense.

I think that Prolog shows better than functional languages the effectiveness of recursion (it doesn't have iteration), and the practical limits we encounter when using it.

CapelliC
  • 59,646
  • 5
  • 47
  • 90
  • 4
    Technically, Haskell doesn't have iteration either – just very clever abstractions around recursion that make it seem like it does when you need it. `forM_` is like a normal `foreach` loop, but it is implemented in terms of mapping over a list of IO actions, and IO actions in turn can emulate mutability but are implemented with chained function calls. – kqr Sep 17 '13 at 22:03
44

Pure functional programming means programming without side effects. Which means, if you write a loop for instance, the body of your loop can't produce side effects. Thus, if you want your loop to do something, it has to reuse the result of the previous iteration and produce something for the next iteration. Thus, the body of your loop is a function, taking as parameter the result of previous execution and calling itself for the next iteration with its own result. This does not have a huge advantage over directly writing a recursive function for the loop.

A program which doesn't do something trivial will have to iterate over something at some point. For functional programming this means the program has to use recursive functions.

Matthew Farwell
  • 60,889
  • 18
  • 128
  • 171
Valentin Perrelle
  • 1,303
  • 1
  • 10
  • 17
  • 1
    silly question - what would be the usefulness of a program which does not produce "side-effects" in a **pure** functional paradigm :-) – peakit Sep 30 '12 at 08:10
  • 9
    In pure functional programming, the result of the program is whatever is returned by the "root" function called. The theory defines the behaviour of program by the relation it creates between inputs and outputs. A Turing machine have a strip of a tape as input, changes it, and the results is what lies at the end of the execution. In functional programming, the input is the parameter of the function and the output is what it returns. We often use [monads](http://en.wikipedia.org/wiki/Monad_(functional_programming)) to handle various types of input/output in functionnal languages. – Valentin Perrelle Sep 30 '12 at 08:17
  • 2
    @peakit you are right the usefulness would be close to zero. So all real languages provide some means of causing side effects. But in languages like haskell they are confined to very special objects (IO-Monads) – Jens Schauder Sep 30 '12 at 10:57
  • @peakit: it is possible to have a pure functional language with no side effect, the most popular pure functional language is Haskell. In pure functional language, functions that do "side effect" (e.g. input/output) are thought of as a function that takes a "universe" as an argument and returns another "universe" (e.g. printLn takes a universe and returns a new universe with the console showing the printed string). Therefore, the language remains totally free of side effect, while still being useful in practice. – Lie Ryan Sep 30 '12 at 11:01
  • 9
    In pure functional languages, monads are nothing special. It's just a syntax sugar for passing the result of an expression to the next statement. A chain of "statements", `a; b; c;` can be thought of as `c(b(a(U)))`, where U is the universe before the statement and each "statement" returns a new universe with the "side effect" of that statement applied. – Lie Ryan Sep 30 '12 at 11:11
  • With lazy evaluation, you don't even need a universe argument. You can think of the IO monad as building a tree of IO actions. That tree is returned to the runtime system by the `main` function, and then the runtime system parses the tree and performs the correct actions as they are encountered. – kqr Sep 17 '13 at 22:08
25

The feature that brings about the requirement that you do things recursively is immutable variables.

Consider a simple function for calculating the sum of a list (in pseudocode):

fun calculateSum(list):
    sum = 0
    for each element in list: # dubious
        sum = sum + element # impossible!
    return sum

Now, the element in each iteration of the list is different, but we can rewrite this to use a foreach function with a lambda argument to get rid of this problem:

fun calculateSum(list):
    sum = 0
    foreach(list, lambda element:
        sum = sum + element # impossible!
    )
    return sum

Still, the value of the sum variable has to be altered in each run of the lambda. This is illegal in a language with immutable variables, so you have to rewrite it in a way that doesn't mutate state:

fun calculateSum([H|T]):
    return H + calculateSum(T)

fun calculateSum([]):
    return 0

Now, this implementation will require pushing to and popping from the call stack a lot, and a program where all small operations would do this would not run very quickly. Therefore, we rewrite it to be tail recursive, so the compiler can do tail call optimization:

fun calculateSum([H|T], partialSum):
    return calculateSum(T, H + partialSum)

fun calculateSum([], partialSum):
    return partialSum

fun calculateSum(list):
    return calculateSum(list, 0)

Of course, if you want to loop indefinitely, you absolutely need a tail recursive call, or else it would stack overflow.

The @tailrec annotation in Scala is a tool to help you analyse which functions are tail recursive. You claim "This function is tail recursive" and then the compiler can tell you if you are mistaken. This is especially important in Scala as compared to other functional languages because the machine it runs on, the JVM, does not support tail call optimization well, so it is not possible to get tail call optimization in Scala in all the same circumstances you would get it in other functional languages.

Magnus Hoff
  • 21,529
  • 9
  • 63
  • 82
  • 1
    Immutable state doesn't mean we cannot re-bind a variable (`sum` in this case), just that the data structure the variable points to can never be altered. But you are right that Erlang for example doesn't allow re-binding of variables. – davidhq Sep 27 '15 at 19:15
  • @davidhq immutable variables and immutable data structures are two different things though. – Will Ness Oct 29 '16 at 10:10
8

TL;DR: recursion is used to handle inductively defined data, which are ubiquitous.

Recursion is natural, when you operate on higher levels of abstraction. Functional programming is not just about coding with functions; it is about operating on higher levels of abstraction, where you naturally use functions. Using functions, it is only natural to reuse the same function (to call it again), from whatever context where it makes sense.

The world is built by repetition of similar/same building blocks. If you cut a piece of fabric in two, you have two pieces of fabric. Mathematical induction is at the heart of maths. We, humans, count (as in, 1,2,3...). Any inductively defined thing (like, {numbers from 1} are {1, and numbers from 2}) is natural to handle/analyze by a recursive function, according to same cases by which that thing is defined/constructed.

Recursion is everywhere. Any iterative loop is a recursion in disguise anyway, because when you reenter that loop, you reenter that same loop again (just with maybe different loop variables). So it's not like inventing new concepts about computing, it's more like discovering the foundations, and making it explicit.


So, recursion is natural. We just write down some laws about our problem, some equations involving the function we're defining which preserve some invariant (under the assumption that the function is coherently defined), re-specifying the problem in simplified terms, and voila! We have the solution.

An example, a function to calculate the length of list (an inductively defined recursive data type). Assume it is defined, and returns the list's length, non-surprisingly. What are the laws it must obey? What invariant is preserved under what simplification of a problem?

The most immediate is taking the list apart to its head element, and the rest - a.k.a. the list's tail (according to how a list is defined/constructed). The law is,

length (x:xs) = 1 + length xs

D'uh! But what about the empty list? It must be that

length [] = 0

So how do we write such a function?... Wait... We've written it already! (In Haskell, if you were wondering, where function application is expressed by juxtaposition, parentheses are used just for grouping, and (x:xs) is a list with x its first element, and xs the rest of them).

All we need of a language to allow for such style of programming is that it has TCO (and perhaps, a bit luxuriously, TRMCO), so there's no stack blow-up, and we're set.


Another thing is purity - immutability of code variables and/or data structure (records' fields etc).

What that does, besides freeing our minds from having to track what is changing when, is it makes time explicitly apparent in our code, instead of hiding in our "changing" mutable variables/data. We can only "change" in the imperative code the value of a variable from now on - we can't very well change its value in the past, can we?

And so we end up with lists of recorded change history, with change explicitly apparent in the code: instead of x := x + 2 we write let x2 = x1 + 2. It makes reasoning about code so much easier.


To address immutability in the context of tail recursion with TCO, consider this tail recursive re-write of the above function length under accumulator argument paradigm:

length xs = length2 0 xs              -- the invariant: 
length2 a []     = a                  --     1st arg plus
length2 a (x:xs) = length2 (a+1) xs   --     the length of 2nd arg

Here TCO means call frame reuse, in addition to the direct jump, and so the call chain for length [1,2,3] can be seen as actually mutating the call stack frame's entries corresponding to the function's parameters:

length [1,2,3]
length2 0 [1,2,3]       -- a=0  (x:xs)=[1,2,3]
length2 1 [2,3]         -- a=1  (x:xs)=[2,3]
length2 2 [3]           -- a=2  (x:xs)=[3]
length2 3 []            -- a=3  (x:xs)=[]
3

In a pure language, without any value-mutating primitives, the only way to express change is to pass updated values as arguments to a function, to be processed further. If the further processing is the same as before, than naturally we have to invoke the same function for that, passing the updated values to it as arguments. And that's recursion.


And the following makes the whole history of calculating the length of an argument list explicit (and available for reuse, if need be):

length xs = last results
  where
     results = length3 0 xs
     length3 a []     = [a]
     length3 a (x:xs) = a : length3 (a+1) xs

In Haskell this is variably known as guarded recursion, or corecursion (at least I think it is).

Will Ness
  • 70,110
  • 9
  • 98
  • 181
7

There are two properties that I consider essential to functional programming:

  1. Functions are first class members (only relevant, because to make it useful the second property is needed)

  2. Functions are pure, i.e. a function called with the same arguments returns the same value.

Now if you program in an imperative style you have to use assignment.

Consider a for loop. It has an index and on each iteration the index has a different value. So you could define a function that returns this index. If you call that function twice you might get different results. Thus breaking the principle no 2.

If you break principle no 2. passing around functions (principle no 1) becomes something extremely dangerous, because now the result of the function might depend on when and how often a function gets called.

repeat
  • 18,496
  • 4
  • 54
  • 166
Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
6

There is nothing 'special' in recursion. It is widespread tool in programming and mathematics and nothing more. However, functional languages are usually minimalistic. They do introduce many fancy concepts like pattern matching, type system, list comprehension and so on, but it is nothing more then syntactic sugar for very general and very powerful, but simple and primitive tools. This tools are: function abstraction and function application. This is conscious choice, as simplicity of the language core makes reasoning about it much easier. It also makes writing compilers easier. The only way to describe a loop in terms of this tools is to use recursion, so imperative programmers may think, that functional programming is about recursion. It is not, it is simply required to imitate that fancy loops for poor ones that cannot drop this syntactic sugar over goto statement and so it is one of the first things they stuck into.

Another point where (may be indirect) recursion required is processing of recursively defined data structures. Most common example is list ADT. In FP it is usually defined like this data List a = Nil | Branch a (List a) . Since definition of the ADT here is recursive, the processing function for it should be recursive too. Again, recursion here is not anyway special: processing of such ADT in recursive manner looks naturally in both imperative and functional languages. Well, In case of list-like ADT imperative loops still can be adopted, but in case of different tree-like structures they can't.

So there is no anything special in recursion. It is simply another type of function application. However, because of limitations of modern computing systems (that comes from poorly made design decisions in C language, which is de-facto standard cross-platform assembler) function calls cannot be nested infinitely even if they are tail calls. Because of it, designers of functional programming languages have to either limit allowed tail-calls to tail recursion (scala) or use complicated techniques like trampoling (old ghc codegen) or compile directly to asm (modern ghc codegen).

TL;DR: There is no anything special in recursion in FP, no more than in IP at least, however, tail recursion is the only type of tail calls allowed in scala because of limitations of JVM.

permeakra
  • 612
  • 6
  • 12
6

Avoiding side effects is one of the pillars of functional programming (the other is using higher order functions).

Imagine how you might make use of imperative flow control without relying on mutation. Is it possible?

Of course for (var i = 0; i < 10; i++) ... depends on mutation (i++). In fact, any conditional loop construct does. In while (something) ... the something will depend on some mutable state. Sure, while (true) ... doesn't use mutation. But if you ever want out of that loop you'll need an if (something) break. Really, try to think of a (non-infinite) looping mechanism other than recursion that doesn't rely on mutation.

What about for (var x in someCollection) ...? Now we're getting closer to functional programming. The x can be thought of as a parameter to the body of the loop. Reusing the name isn't the same as reassigning a value. Perhaps in the body of the loop you're yield returning values as an expression in terms of x (no mutation).

Exactly equivalently, you could move the body of the for loop into the body of a function foo (x) ... and then map that over someCollection using a higher order function - replacing your loop construct with something like Map(foo, someCollection).

But then how is the library function Map implemented without mutation? Well, using recursion of course! It's done for you. It becomes less common to have to implement recursive functions yourself once you begin making use of the second pilar of higher order functions to replace your looping constructs.

Additionally, with tail call optimization a recursive definition is equivalent to an iterative process. You might also enjoy this blog post: http://blogs.msdn.com/b/ashleyf/archive/2010/02/06/recursion-is-the-new-iteration.aspx

AshleyF
  • 3,682
  • 1
  • 25
  • 23
  • It's definitely possible to use imperative flow control without explicit loops, and various programming languages do so: APL, J, Sisal, Vector Pascal, to name a few. (If that doesn't count because the loop is still going on behind the scenes, then all functional languages are disqualified by the same logic.) Another approach is a data flow system where each processor has its own stateless, endless loop, and you chain them together to form an assembly line... Then you make the line itself go in a circle. :) – tangentstorm Sep 30 '12 at 23:46
  • 1
    The point was that expressing iterative processes _without using mutation_ requires expressing them recursively. Expressing without explicit loops is easy (e.g. the `Map` example mentioned). – AshleyF Oct 01 '12 at 00:21
  • 1
    Yes but recursion itself relies on mutable state: the thing that's mutating is the call stack in the interpreter. It's just hidden from the developer. The array programming languages also hide the mutation, but it's not recursion under the hood. In J, you can say 'i. 5' and get '0 1 2 3 4', or '1 + i. 5' and you get '1 2 3 4 5'. Any mutable state involved is at the interpreter level, just like with lambda calculus style languages... But under the hood, it could be doing a loop or it could be doing all the addition steps in parallel (with intel's mmx / sse instructions, or your graphics card) – tangentstorm Oct 01 '12 at 22:47
  • 1
    Ah, yes. I think I see your point about when I say, `Map` is implemented "...with recursion of course!". Not necessarily true. Not knowing J, I relate your example to F#: You can say `[0..4]` and get `[0; 1; 2; 3; 4]` or say `List.map ((+) 1) [0..4]` and get `[1; 2; 3; 4; 5]`. Under the hood `List.map` may use mutation as it sees fit. Indeed, it's all loads/stores at some point. The nice thing is to leave that kink of approach up to the compiler and library authors. We got away from `goto` years ago (though it's all branches at some point). Now let's get away from `load` and `store`. – AshleyF Oct 02 '12 at 01:42
2

Last time I used a Functional language (Clojure) I was never even tempted to use recursion. Everything could be dealt with as a set of things, to which a function was applied to get part products, to which another function was applied, until the final result was reached.

Recursion is only one way, and not necessarily the clearest way, to handle the multiple items you usually have to handle to deal with any g

1

For the sake of new FP learners I would like to add my 2 cents.As mentioned in some of the answers, recursion is their to make use of immutable variables but why we need to do that ? its because it makes it easy to run the program on multiple cores in parallel, but why we want that ? Can't we run it in single core and be happy as always we have been ? No because content to process is increasing day by day and CPU Clock cycle can't be increased so significantly than adding more cores. From past one decade the clock speed has been around upto 2.7 ghz to 3.0 ghz for consumer computers and chip designers are having issues in fitting more and more transistors in their.Also FP has been their from very long time, but didn't pick up as it used recursion and memory was very expensive in those days but as clock speeds were soaring year after year so community decided to keep on going with OOP Edit: it was quite fast, I had only couple of minutes

Eklavyaa
  • 370
  • 5
  • 17
  • 1
    In other words: in the 'old days' functional programming came to a halt, because of hardware limitations. Nowadays, functional programming is the answer to... hardware limitations. Got to love the irony of that. – Jacco Jun 04 '17 at 20:25