Recursion, Misunderstood

My experience with the Electrical Engineers in academics is that they usually view Computer Science in terms of circuits and details of how things work at the most tangible level. One hazard of using this approach in teaching is that many times you end up with some wrong core concepts of Computer Science. They enable your abstract level thinking at large. The worst part is, you don't even know that your concepts are incorrect yet you are very confident about them.

My undergrad CS program at FAST-NUCES was heavily dominated by dedicated and competent Electrical Engineers. That is why my classmates and I have really good C/C++ programming skills, great concepts about pointers, indepth understanding of microprocessors work, how implementations of Operating Systems take advantage of them at the very nitty gritty level and have strong knowledge of other implementation specific things. On the downside, I feel such students have some gaps left in their personalities towards the mathematical face of Computer Science.

Perhaps the most commonly misunderstood such topic is Recursion. For me, it was a shock to know, after about two years of my graduation, that what I knew about recursion was quite wrong. I, along with many others were taught (and are still being taught) that recursion is about a function calling itself.  When a function calls itself, a separate call is made, the parameters and some other info is placed on stack and a new 'instance' of function takes over until it returns. If this process goes on infinitely, the program would end with an overflowed stack so there must be a base or terminating condition. The instructors would give us assignments in which recursion had to be "removed" by explicitly storing some state information on a stack so that a new call to the same function was not made. The emphasis of these assignments was that although "recursive" code is simple, recursion has a huge overhead which should be removed in most of the cases.

This is not recursion! This way of looking at recursion might be OK for a low-level C/C++ programmer who wants to build his career coding micro-controllers for the rest of his life. But these concepts of recursion are certainly disastrous for a Computer Science Major who wants to truly appreciate the beauty of algorithms.

I was lucky to learn Lisp after my graduation which gave enough abstraction from low level nitty gritty controls such as memory management and pointers as well as complex syntax, to let me focus on the problem itself rather than going into syntax.

What I learnt then is that recursion is solving a problem by deferring until it's subproblem and reconciling the solution of the problem with a simpler computation. The subproblem itself is solved in the same manner until the subproblem becomes so simple that it's solution is trivial. In mathematics, this is called inductive step.

This seems much like the former view but it's implication is quite different. The second definition of recursion implies that a function calling itself may not necessarily be forming a recursive solution. For example the following two implementations of factorial call themselves but the first one is recursive while the second one is iterative:



; Recursive
(define fact
 (lambda (n)
 (if (= n 0) 1
 (* n (factorial (- n 1))))))
 
; Iterative
(define facti
 (lambda (n, c)
 (if (= n 0) c
 (facti (- n 1) (* c n))

The reason facti is not recursive is that it is not deferring any computation.

The equal implementation of these in Python would be:

# Recursive
def fact(n):
 if n == 0: return 1
 return n * fact(n - 1)
 
# Iterative
def facti(n, c=1):
 if n == 0: return c
 else: return facti(n - 1, c * n)

Also, a function not calling itself may still be a recursive function. For example, an explicit stack storing different states to avoid function calling itself is still recursive!

 

PS: If you still don't get what recursion is, see "Recursion, Misunderstood" by Sharjeel Ahmed Qureshi.

What Lisp & Assembly instill

My Assembly Programming Language teacher, Belal Hashmi Sahib said in the first lecture of the course, “Assembly is extremely simple. It is so simple that students don’t expect such simplicity and hence it starts appearing complex to them”. Then he wrote MOV AX, BX on the board, explained it and asked if everyone understood it. Everybody nodded. He asked again to make sure there was not even a slightest bit of confusion. It seemed very simple and trivial to everyone. He proceeded by saying “This is the end of the assembly language programming course. The rest of the course is just the revision of MOV AX,BX”. Everybody thought it was a joke but later it turned out that assembly indeed ended at MOV AX, BX. The rest of the course was about learning other concepts to fully utilize the potential of MOV AX, BX.

Last year I learned Lisp on my own and then TAed a course on Lisp at LUMS with Dr. Umar Saif. In the first lecture the first thing Dr. Umar taught was (lambda (x) (+ x x)). At this moment I realized that just like Assembly, Lisp had finished! Because if students had understood it, there was nothing much about the language left. All they needed now was to understand some important concepts such as recursion, closures etc. to use Lisp.

What is common between Assembly and Lisp? Both of these two languages are very simple to learn as they have little syntax or fancy language features. However the process of learning them compels one to grasp some important concepts. For instance by learning assembly you are bound to understand some flavor of computer architecture, interrupts, timers and other hardware related stuff. Similarly by learning Lisp you automatically get to know functional programming, interpretation, recursion, closures etc.

A programmer familiar with assembly can easily understand the use of unions, options such as __decl and other such features of C because he knows the underlying magic. Similarly if one wants to understand the philosophy of the features of Javascript, Python & Ruby I strongly recommend learning Lisp.