Learning Recursion v0 - 1
Learning Recursion v0 - 1
Recursion
Alvin Alexander
Copyright
Learning Recursion
This book is presented solely for educational purposes. While best efforts
have been made to prepare this book, the author makes no representa-
tions or warranties of any kind and assumes no liabilities of any kind
with respect to the accuracy or completeness of the contents, and specif-
ically disclaims any implied warranties of merchantability or fitness of
use for a particular purpose. The author shall not be held liable or re-
sponsible to any person or entity with respect to any loss or incidental or
consequential damages caused, or alleged to have been caused, directly
or indirectly, by the information or programs contained herein. Any use
of this information is at your own risk.
1
https://alvinalexander.com
Other books
2
https://amzn.to/3du1pMR
3
https://alvinalexander.com/scala/functional-programming-simplified-book
Learn Functional Programming Without Fear
(“The Little FP Book,” alvinalexander.com)4
4
https://alvinalexander.com//scala/learn-functional-programming-book
5
https://alvinalexander.com/scala/learn-scala-3-the-fast-way-book
Contents
1 Welcome 1
2 Recursion: Introduction 3
3 Recursion: Motivation 5
4 Recursion Background: Let’s Look at Lists 9
5 Recursion: How to Write a ‘sum’ Function 17
6 Recursion: How Recursive Function Calls Work 25
7 Visualizing the Recursive sum Function 31
8 Recursion: A Conversation Between Two Developers 39
9 Recursion: Thinking Recursively 43
10 JVM Stacks, Stack Frames, and Stack Overflow Errors 53
11 A Visual Look at Stacks and Frames 61
12 Tail-Recursive Algorithms 71
13 Bonus: Processing I/O with Recursion 85
14 The End 101
i
ii CONTENTS
1
Welcome
The way this booklet came into being is that in January, 2023, I started
updating my book, Functional Programming, Simplified1 , to use Scala 3
and other modern functional programming (FP) libraries like ZIO2 and
Cats Effect3 . When I got to these chapters on recursion, I realized that
they were almost 100 pages long all by themselves, so I decided to pull
them out into this booklet for any programmers who are interested in
recursion, but aren’t interested in all the other FP content in that book.
1
https://alvinalexander.gumroad.com/l/fpsimplified
2
https://zio.dev
3
https://typelevel.org/cats-effect
1
2 Welcome
Goals
I also usually include a “Goals” chapter in each book, but again, that’s
pretty apparent for this book: The goal is to share a collection of lessons
about programming using recursion.
4
https://alvinalexander.gumroad.com/l/fpsimplified
2
Recursion: Introduction
The one exception I’ll add to that statement is that you may still want to
review the lessons on stacks, stack frames, and tail recursion, because
they add more knowledge on top of the “basic recursion” technique.
3
4 Recursion: Introduction
3
Recursion: Motivation
What is recursion?
Before getting into the motivation to use recursion, a great question is,
“What is recursion?”
Simply stated, a recursive function is a function that calls itself. That’s it.
The short answer is that algorithms that use for loops require the use
of var fields, and as I mention in my book, Functional Programming, Sim-
plified, we never us var fields or mutable data structures in functional
programming (FP).
5
6 Recursion: Motivation
Please recall that these lessons come from my FP book. That’s why this
answer comes from an FP perspective.
This algorithm uses a var field named sum and a for loop to iterate
through every element in the list that’s passed into the function as the
input parameter xs. In Scala this is how you use this function to print
the sum of a small list of integers:
One problem is that reading a lot of custom for loops dulls your brain.
about the way you thought when you read that function, one of the first
things you thought is, “Hmm, here’s a var field named sum, so Al is
probably going to modify that field in the rest of the algorithm.” Next,
you thought, “Okay, here’s a for loop … he’s looping over xs … ah, yes,
he’s using +=, so this really is a ‘sum’ loop, so that variable name makes
sense.”
Once you learn FP — or even if you just learn the functional methods
available on the Scala collections classes — you realize that’s an awful lot
of thinking about a little-bitty custom for loop.
If you’re like me a few years ago, you may be thinking that what I just
wrote is overkill. You might think, “I look at mutable variables and cus-
tom for loops all the time; what’s the big deal?”
Therefore, one can argue that when you have to read a custom for loop
like that one, you’re filling up those short-term memory slots with mostly
useless information. Put differently, since we can only keep just so much
information in our brains at one time:
• Any time we can keep less information there, it’s a win, and
• Boilerplate for loop code is a waste of our brain’s RAM (and
CPU)
Maybe this seems like a small, subtle win at the moment, but speaking
from my own experience, anything I can do to keep my brain’s RAM
free for important things is a win.
1
https://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus_or_Minus_
Two
8 Recursion: Motivation
My experience was that once you let go of var fields and for loops, you
can discover a different way to solve iterative problems.
What to do?
If you’re like me, at first you’ll NEED to write recursive functions be-
cause that’s all you’re allowed to do in FP, but after a while you’ll WANT to
write recursive functions.
4
Recursion Background:
Let’s Look at Lists
a
https://en.wikipedia.org/wiki/Linked_list
Visualizing lists
Because the List data structure — and the head and tail components of
a List — are so important to recursion, it helps to visualize what a list
and its head and tail components look like. Figure 4.1 shows one way to
visualize a List.
Figure 4.1: One way to visualize the head and tail components of a list.
This creative imagery comes from the online version of “Learn You a
9
10 Recursion Background: Let’s Look at Lists
Haskell for Great Good”1 , and it does a terrific job of imprinting the
concept of head and tail components of a list into your brain. As shown,
the “head” component is simply the first element in the list, and the
“tail” is everything else — the rest of the list.
A slightly more technical way to visualize the head and tail of a list is
shown in Figure 4.2.
An even more accurate way to show this is with a Nil value at the end of
the List, as shown in Figure 4.3, because that’s what a List really looks
like in Scala.
To be clear, the List that I’m talking about is a linked list — the Scala
immutable List2 , which is the default list you get if you type List in your
IDE or the REPL:
1
http://learnyouahaskell.com/starting-out
2
https://www.scala-lang.org/api/current/scala/collection/immutable/List.html
11
As shown in Figure 4.4, this List is a series of cells, where each cell con-
tains two things: (a) a value, and (b) a pointer to the next cell.
What’s extremely important for our needs is that the last cell in a linked
list contains the Nil value. This value being in the last cell is important
because it’s how your recursive Scala code will know when it has reached
the end of a List.
Building on that previous image, Figure 4.5 highlights the head element
of a list, and Figure 4.6 highlights the tail elements. Just like the Haskell
programming language — and Lisp before it — the default Scala List
works with these head and tail components, and I’ll use them extensively
in the examples that follow.
12 Recursion Background: Let’s Look at Lists
For historical reasons these cells are known as cons cells. That name
comes from Lisp, and if you like history, you can read more about it on
Wikipediaa .
a
https://en.wikipedia.org/wiki/Cons
Figure 4.7: A list with no elements contains only one cell, which is the Nil element.
Because I didn’t give those lists a data type (like Int), the results look a
little unusual, and as a practical matter you probably won’t do this in
the real world. What you will do is specify a data type when you create
your lists, like this:
There are several ways to create non-empty Lists in Scala, but for the most
part I’ll use two approaches. First, here’s the most-common approach:
These two techniques result in the exact same List[Int], which you can
see in the REPL:
The second approach is known as using “cons cells.” As you can see,
it’s a very literal approach to creating a List, where you specify each
element in the List, including the Nil element, which must be in the
last position. The elements you specify are literally the values in each
cons cell. Note that if you forget the Nil element at the end, the Scala
compiler will give you an error message:
--------------------------
1 |val list = 1 :: 2 :: 3
| ^^^^
| value :: is not a member of Int
1 error found
With all of the images of the previous lesson firmly ingrained in your
brain, let’s write a sum function using recursion!
let’s start tackling the problem in the usual way, by thinking, “Sketch the
function signature first.”
What do we know about the sum function we want to write? Well, right
off the bat we know a couple of things:
17
18 Recursion: How to Write a ‘sum’ Function
Armed with those two pieces of information, I sketch the signature for
a sum function like this:
Now that we have a little idea of how to think about the problem recur-
sively, let’s see how to implement those sentences in Scala code.
TIP: Thinking about a List in terms of its head and tail elements is a
standard way of thinking when writing recursive functions.
19
This is a Scala way of saying, “If the List is empty, yield 0.” If you’re
comfortable with match expressions and the List class, I think you’ll
agree that this makes sense.
TIP: When writing a recursive function, write the end condition first, just
like this. This will give you comfort that the recursive calls will stop when
this condition is reached.
(That being said, if you are an OOP developer who’s used to using lan-
guages like Java, Kotlin, Python, Dart, etc., I was once in your shoes,
and I remember that using return can help when you first start writing
recursive functions. Therefore, perhaps a more gentle way to say this is,
“please drop the return keyword from your vocabulary as soon as you
feel comfortable letting it go.”)
You can also write this function using an if/then expression, but be-
cause pattern matching is such a big part of Scala and FP, I prefer match
expressions.
Because Nil is equivalent to List() in Scala, you can also write that case
expression like this:
However, most functional programmers use Nil, and I’ll continue to use
Nil in this lesson.
which says, “If the list is not empty, the result of the algorithm is the
combination of (a) the value of its head element, and (b) the sum of its
tail elements.”
Since the first case expression handles the case of an empty list, our sec-
ond case expression should handle the case of a non-empty list. Further-
more, knowing that we want to pattern-match a List and split it into
head and tail components, I start writing the second case expression
like this:
If you know your case expressions, you know that if sum is given a list
like List(1,2,3,4), the result of this code is that it assigns the value 1 to
head, and then tail is assigned the value List(2,3,4), like this:
head = 1
tail = List(2, 3, 4)
(If you don’t know your case expressions, please refer to the
match expression lessons in the Scala Cookbook.)
Great, this case expression is a start! But, how do we finish it? Once
again I go back to the second sentence:
If the list is not empty, the result of the algorithm is the com-
bination of (a) the value of its head element, and (b) the sum
of the tail elements.
The “value of its head element” is easy to add to the case expression:
But then what comes next? The sentence “the value of its head element,
and the sum of the tail elements,” tells us we’ll be adding something to
head:
What are we adding to head? The sum of the list’s tail elements. Hmm, now
how can we get the sum of a list of tail elements? Well, what if we happen
to have a function nearby that gives us the sum of a list of integer values?:
If you combine this new case expression with the existing code, you get
the following complete sum function:
TIP: If it ever feels weird to call the same function you’re currently writing,
just imagine that you’re calling some other function. For example, in this
case, instead of calling sum(tail), imagine you’re calling some other
function named addAllElements(tail) or something like that.
23
Also, if you’re new to Scala, it may be easier to read this function if I put
the values on the right side of the => symbol on separate lines:
If you’re not familiar with match expressions, the way cases work inside
a match expression is that the code on the left side of the => symbol is the
pattern you’re matching, and the code on the right side of the => is the
code that will be run when list matches the current pattern.
If you’re new to case expressions, it’s important to note that the head
and tail variable names in the second case expression can be anything
you want. I wrote it like this:
or this:
This last example uses variable names that are commonly used with FP,
lists, and recursive programming. When working with a list, a single el-
ement is often referred to as x (pronounced “ex”) and multiple elements
are referred to as xs (pronounced “exes”). It’s a way of indicating that
x is singular and xs is plural, like referring to a single “pizza” or mul-
24 Recursion: How to Write a ‘sum’ Function
tiple “pizzas.” With lists, the head element is definitely singular, while
the tail can contain one or more elements. I’ll generally use this naming
convention in this book.
When you run this application you should see the output, 10. And if
you’ve never written a recursive function before, congratulations!
“That’s great,” you say, “but how exactly did that end up printing 10?”
To which I say, “Excellent question. Let’s dig into that in the next les-
son!”
As I’ve noted before, I tend to write verbose code that’s hopefully easy
to understand — especially in books — but you can shrink the last three
lines of code to this, if you prefer:
println(sum(List(1, 2, 3, 4)))
6
Recursion: How Recursive Function
Calls Work
When recursive calls are made to a function like sum, you can envision
that they “wind up” as sum is called repeatedly. You can imagine that
the second case expression looks like this as sum is called over and over
again:
In the case of the sum function, the end condition is triggered when the
Nil element in a List is reached. (Recall that Nil is the same as List()).
When sum is passed the Nil element as the last element of the List, this
pattern of the match expression is matched:
Because this line simply returns 0, there are no more recursive calls to
sum. As I’ve mentioned, handling the Nil element is the typical way
of ending the recursion when you operate on all elements of a List in
recursive algorithms.
25
26 Recursion: How Recursive Function Calls Work
1 :: 2 :: 3 :: Nil // List[Int]
'a' :: 'b' :: 'c' :: Nil // List[Char]
"a" :: "b" :: "c" :: Nil // List[String]
This is a reminder that with ANY Scala List you are guaranteed that the
last List element is Nil. Therefore, if your algorithm is going to iterate
over the entire list, you should use this as your function’s end condition:
This is our first clue about how the “unfolding” process works.
A good way to understand how the sum function example runs is to add
println statements inside the case expressions. First, change the sum
function to look like this:
Now when you call it with a List(1,2,3,4) as its input parameter, you’ll
see this output:
That output shows that sum is called repeatedly until the list is reduced
to List() (which is the same as Nil). When List() is passed to sum, the
first case is matched and the recursive calls to sum come to an end. (I’ll
demonstrate this visually in the next lesson.)
The book Land of Lisp1 states, “recursive functions are ‘list eaters,’” and
this output shows exactly why that statement is true.
1. The first time sum is called, the match expression sees that the given
List does NOT match the Nil element, so control flows to the
second case statement.
1
https://amzn.to/3WRp6Cs
28 Recursion: How Recursive Function Calls Work
2. The second case statement DOES matches the List pattern, then
splits the incoming list of 1::2::3::4::Nil into (a) a head element
of 1 and the remainder of the list, 2::3::4::Nil. The remainder
— the tail — is then passed into another sum function call.
4. That statement matches the List pattern, then splits the list into
a head element of 2 and a tail of 3::4::Nil. The tail is passed as
the input parameter to another sum call.
5. A new instance of sum receives the list 3::4::Nil. This list does
not match the Nil element, so control passes to the second case
statement.
6. The list matches the pattern of the second case statement, which
splits the list into a head element of 3 and a tail of 4::Nil. The
tail is passed as the input parameter to another sum call.
7. A new instance of sum receives the list 4::Nil, sees that it does not
match Nil, and passes control to the second case statement.
8. The list matches the pattern of the second case statement, and it’s
split into a head element of 4 and a tail of Nil. The tail is passed
to another sum function call.
10. The first case expression returns the value 0. This marks the end
of the recursive calls.
At this point — when the first case expression of this sum instance returns
0 — all of the recursive calls “unwind” until the very first sum instance
returns its answer to the code that called it.
29
That description gives you an idea of how the recursive sum function calls
work until they reach the end condition. Here’s a description of what
happens after the end condition is reached:
1. The last sum instance — the one that received List() — returns
0. This happens because List() matches Nil in the first case ex-
pression.
2. This returns control to the previous sum instance. The second
case expression of that sum function has return 4 + sum(Nil) as
its return value. This is reduced to return 4 + 0, so this instance
returns 4. (Note that I don’t use a return statement in the actual
code, but I find that the following examples are easier to read
when I use return.)
3. Again, this returns control to the previous sum instance. That sum
instance has return 3 + sum(List(4)) as the result of its second
case expression. You just saw that sum(List(4)) returns 4, so this
case expression evaluates to return 3 + 4, or 7.
5. Finally, control is returned to the original sum function call. Its sec-
ond case expression is return 1 + sum(List(2,3,4)). You just
saw that sum(List(2,3,4)) returns 9, so this call is reduced to
return 1 + 9, or 10. This value is returned to whatever code
called the first sum instance.
One way to visualize how the recursive sum function calls work — the
“going down” part — is shown in Figure 6.1.
30 Recursion: How Recursive Function Calls Work
Figure 6.1: How the original sum call leads to another, then to another …
After that, when the end condition is reached, the “coming back up”
part — what I call the unwinding process — is shown in Figure 6.2.
Figure 6.2: How sum function calls unwind, starting with the last sum call.
If this isn’t clear, fear not, in the next lesson I’ll show a few more visual
examples of how this works.
7
Visualizing the Recursive sum
Function
Figure 7.1: This rectangular symbol is used to represent functions in this lesson.
31
32 Visualizing the Recursive sum Function
The top cell in the rectangle indicates that this first instance of sum is
called with the parameters 1,2,3. Note that I’m leaving the “List”
name off of these diagrams to make them more readable.
The body of the function is shown in the middle region of the symbol,
and it’s shown as return 1 + sum(2,3). As I mentioned before, you
don’t normally use the return keyword with Scala/FP functions, but in
this case it makes the diagram more clear. Note that in this example, h
stands for head, and t stands for tail.
In the bottom region of the symbol I’ve left room for the final return
value of the function. At this time we don’t know what the function will
return, so for now I just leave that spot empty.
For the next step of the diagram, we know that the first sum function
call receives the parameter list (1,2,3), and its body now calls a new
instance of sum with the input parameter sum(2,3) (or sum(List(2,3)),
if you prefer). You can imagine the second case expression separating
the List into head (h) and tail (t) elements, as shown in Figure 7.3.
Figure 7.3: The first sum function invokes a second sum function call.
Then this sum instance makes a recursive call to another sum instance, as
33
Figure 7.4: The second sum function call begins to invoke the third sum instance.
Again I leave the return value of this function empty because I don’t
know what it will be until its sum call returns.
It’s important to be clear that these two function calls are completely
different instances of sum. They have their own input parameter lists,
local variables, and return values. It’s just as if you had two different
functions, one named sum3elements and one named sum2elements, as
shown in Figure 7.5.
Figure 7.5: One sum function calling another sum instance is just like calling a
different function.
34 Visualizing the Recursive sum Function
Getting back to the sum example, you can now imagine that the next
step will proceed just like the previous one, as shown in Figure 7.6.
Figure 7.6: The third sum function has now been called as sum(List(3), so its
head is 3 and its tail is Nil.
Now we’re at the point where we make the last recursive call to sum. In
this case, because 3 was the last integer in the list, a new instance of
sum is called with the Nil value as its input parameter. This is shown in
Figure 7.7.
With this last sum call, the Nil input parameter matches the first case
expression, and that expression simply returns 0. So now we can fill in
the return value for this function, as shown in Figure 7.8.
Now this sum instance returns 0 back to the previous sum instance, as
shown in Figure 7.9.
The result of this function call is 3 + 0 (which is 3), so you can fill in
its return value, and then flow it back to the previous sum call. This is
35
Figure 7.7: Nil is passed into the final sum function call.
The result of this function call is 2 + 3 (5), so that result can flow back
to the previous function call, as shown in Figure 7.11.
Finally, the result of this sum instance is 1 + 5 (6). This was the first sum
function call, so it returns the value 6 back to whoever called it, as shown
in Figure 7.12.
NOTE: There are four recursive calls because there are four elements in
the list: the values 1, 2, and 3, as well as the trailing Nil element.
37
Figure 7.12: The first sum call returns to the final result.
Other visualizations
There are other ways to draw recursive function calls. Another nice
approach is to use a modified version of a UML “Sequence Diagram,”
as shown in Figure 7.13. Note that in this diagram, “time” flows from
the top to the bottom.
Figure 7.13: The sum function calls can be shown using a UML Sequence Diagram.
This diagram shows that the main method calls sum with the parameter
List(1,2,3), where I again leave off the List part; it calls sum(2,3), and
so on, until the Nil case is reached, at which point the return values flow
38 Visualizing the Recursive sum Function
back from right to left, eventually returning 6 back to the main method.
You can write the return values like that, or with some form of the func-
tion’s equation, as shown in Figure 7.14.
Figure 7.14: Writing the function return values as “head + sum(tail)” equations.
Summary
Those are some visual examples of how recursive function calls work. If
you find yourself struggling to understand how recursion works, I hope
these diagrams are helpful.
8
Recursion: A Conversation Between
Two Developers
I hope this “conversation” will help drive home some of the points about
how recursion works:
Person 1 Person 2
What is this? An expression that defines a
val xs = List(1,2,3,4) List[Int], which in this case
contains the integers 1 through 4.
The expression binds that list to the
immutable variable xs.
1
https://amzn.to/3WV8N7S
39
40 Recursion: A Conversation Between Two Developers
Person 1 Person 2
How about this? That’s the remaining elements in
xs.tail the list xs, which is List(2,3,4).
Person 1 Person 2
Please continue. A new instance of sum is called with
the parameter List(3).
What happens inside this instance It receives List(). This is the same
of sum? as Nil, so it matches the first case.
Okay, so now what happens? This ends the recursion, and then
the recursive calls unwind, as
described in the previous lesson.
42 Recursion: A Conversation Between Two Developers
9
Recursion: Thinking Recursively
This lesson has one primary goal: to show that the thought process fol-
lowed in writing the sum function follows a common recursive program-
ming “pattern.” Indeed, when you write recursive functions you’ll gen-
erally follow the three-step process shown in this lesson.
I don’t want to make this too formulaic, but the reality is that if you
follow these steps in your thinking, it will make it easier to write recursive
functions, especially when you first start.
43
44 Recursion: Thinking Recursively
Let’s take a deep dive into each step in the process to make more sense
of these descriptions.
Once I know that I’m going to write a recursive function — or any pure
function, for that matter — the first thing I ask myself is, “What is the
type signature of this function?”
I find that if I can describe a function verbally, I can quickly figure out
(a) the parameters that will be passed into it and (b) what the function
will return. In fact, if you don’t know these things, you’re not ready to
write the function yet, are you?
“If I were given one hour to save the planet, I’d spend 59
minutes defining the problem, and one minute resolving it.”
~ Albert Einstein
In the sum function, the algorithm is to “add all of the integers in a given
list together to return a single integer result.” Therefore, because I know
the function takes a list of integers as its input, I can start sketching the
function signature like this:
Because the description also tells me that the function returns a single
Int result, I add the function’s return type:
45
This is the Scala way to say that “the sum function takes a list of integers
and returns an integer result,” which is what I want. In FP, sketching
the function signature is often half of the battle, so this is actually a big
step.
When writing a recursive function, the next thing I usually think is,
“How will this algorithm end? What is its end condition?”
Because a recursive function like sum keeps calling itself over and
over, it’s of the utmost importance that there is an end case. If a
recursive algorithm doesn’t have an end condition, it will keep calling
itself as fast as possible until either (a) your program crashes with a
StackOverflowError, or (b) your computer’s CPU gets extraordinarily
hot. Therefore, I reiterate this tip:
In the sum algorithm you know that you have a List, and you want to
march through the entire List to add up the values of all of its elements.
You may not know it at this point in your recursive programming ca-
reer, but right away this statement is a big hint about the end condition.
Because:
you can start to write the end condition case expression like this:
46 Recursion: Thinking Recursively
Now the next question is, “What should this end condition return?”
A clue here is that the function signature states that it returns an Int.
Therefore, you know that this end condition must return an Int of some
sort. But what Int?
Because this is a “sum” algorithm, you also know that you don’t want
to return anything that will affect the sum. Hmm … what Int can you
return when the Nil element is reached that won’t affect the sum of a
list of integers?
The answer is 0.
NOTE: There’s a general rule about this thought process, and I’ll share it
shortly.
That condition states that if the function receives an empty List — de-
noted by Nil — the function will return 0.
I’ll expand more on the point of returning 0 in this algorithm in the coming
lessons, but for now it may help to know that there’s a mathematical theory
involved in this decision. Per this Wikipedia pagea , “In mathematics, an
identity element (or neutral element) of a binary operation operating on a
set is an element of the set that leaves unchanged every element of the
set when the operation is applied.”
Put in simpler words, in our example (a) our set is a List[Int], and
(b) our operation is our sum algorithm. Given that combination of
set+operation, the identity element is the value 0, because when you
add 0 to any other integer value, it has no effect on that value. (As
alluded to in the description, the value 0 is “neutral” for this combination
of set+operation.)
To help drive this home, here are a few other identity elements for different
set+operation combinations:
1) Imagine that you want to write a “product” algorithm for a list of integers.
What would you return for the end condition in this case?
As a reminder, we need some neutral value so that when any integer value
is multiplied by it, the resulting value is the same as the initial value. That
is, given this equation:
a = identityElement * 100
the requirement is that a must also be 100. Therefore, what must the
value of identityElement be?
The correct answer is 1. This is because when any integer value is multi-
plied by 1, the result is the same as the original integer. (The number 1 is
the neutral element for the set+operation combination of (a) a List[Int]
combined with (b) a product algorithm.)
48 Recursion: Thinking Recursively
a = identityElement + "foo"
The correct answer is a blank space. For the combination of (a) a list (or
set) of strings, and (b) an addition algorithm, a blank space has no effect
on the final result.
a
https://en.wikipedia.org/wiki/Identity_element
Getting back to our sum algorithm, now that you’ve defined the func-
tion signature and the end condition, the final question is, “What is the
algorithm at hand?”
The answer for a “sum” function is that it should add all of the elements
in the list.
A common way to write the pattern for this case expression is this:
This pattern is the Scala way to say, “head will be bound to the value of
the first element in the List, and tail will contain all of the remaining
elements in the List.”
Because my description of the algorithm states that the sum is “the sum
of the head element, plus the sum of the tail elements,” I now start to
write the algorithm that goes on the right side of the => symbol. I start
by adding the head element:
and then I add this code to represent “plus the sum of the tail elements”:
Now that we have the function signature, the end condition, and the
list-processing algorithm, we have the complete function:
Before I move on, if you’re new to Scala it can help to see the return
value on the right side of the => symbol on its own line:
That helps by separating the pattern-matching on the left side of the =>
from the resulting value on its right side.
Also, almost nobody in the Scala community uses the return keyword,
but if you’re coming to Scala from an OOP language, I understand that
adding it in can make your code easier to read initially:
But in the long run, remember that pure, algebraic functions don’t “re-
turn” a value; they evaluate to a result (so drop the return keyword as soon
as you can).
Naming conventions
As I noted in the previous lessons, when FPers work with lists, they often
prefer to use the variable name x to refer to a single element and xs
to refer to multiple elements, so you’ll also see recursive functions that
process lists use these variable names:
But you don’t have to use any of those names; use whatever names work
best for you.
But the last two steps — defining the end condition and writing the
algorithm — are interchangeable, and even iterative. For instance, if
you’re working on a List and you want to do something for every element
in the list, you know the end condition will occur when you reach the
Nil element. But if you’re NOT going to operate on the entire list, or if
you’re working with something other than a List, it can help to bounce
back and forth between the end case and the main algorithm until you
come to the solution.
Key points
As a recap of the key concepts in this lesson, when I sit down to write a
recursive function, I generally think of three things:
To solve the problem I almost always write the function signature first,
and after that I usually write the end condition next, though the last two
steps can also be an iterative process.
52 Recursion: Thinking Recursively
Another key point is knowing the identity (neutral) element for the com-
bination of set+algorithm that you’re writing.
As a final note for this lesson, I thought about showing how to write a map
function using recursion, but as I thought about it, I realized you don’t
need to use recursion for that algorithm, you just need a for expression.
I show how to do that in my blog post, How to Write a ‘map’ Function
in Scala1
1
https://alvinalexander.com/scala/fp-book/how-to-write-scala-map-function
10
JVM Stacks, Stack Frames, and
Stack Overflow Errors
For instance, if you run the sum function from the previous lessons with
a larger list, like this:
53
54 JVM Stacks, Stack Frames, and Stack Overflow Errors
I’ll cover tail recursion in the next lesson, but in this lesson I want to
discuss the JVM stack and stack frames. If you’re not already familiar with
these concepts, this discussion will help you understand why this code
results in an exception. It can also help you debug “stack traces” in
general.
What is a “Stack”?
Oracle provides the following description of the stack and stack frames
as they relate to the JVM:
Given that description, you can visualize that a single stack has a pile of
stack frames that look like Figure 10.1.
55
As that description mentions, each thread has its own stack, so in a multi-
threaded application there are multiple stacks, and each stack has its own
stack of frames, as shown in Figure 10.2.
To explain the stack a little more, all of the following quoted text comes
from the free, online version of a book titled, Inside the Java Virtual
Machine, by Bill Venners (who is also known for creating the ScalaTest
56 JVM Stacks, Stack Frames, and Stack Overflow Errors
testing framework). (I edited the text slightly to include only the portions
relevant to stacks and stack frames.)
The same chapter in that book describes the “stack frame” as follows:
“The stack frame has three parts: local variables, operand stack, and
frame data.”
57
That’s important: the size of a stack frame varies depending on the local variables
and operand stack. The book describes that size like this:
These descriptions introduce the phrases word size, operand stack, and
constant pool. Here are definitions of those terms:
58 JVM Stacks, Stack Frames, and Stack Overflow Errors
First, word size is a unit of measure. From Chapter 5 of the same book,
the word size can vary in JVM implementations, but it must be at least
32 bits so it can hold a value of type long or double.
The Java run-time constant pool is defined at this oracle.com page, which
states, “A run-time constant pool … contains several kinds of constants,
ranging from numeric literals known at compile-time, to method and
field references that must be resolved at run-time. The run-time con-
stant pool serves a function similar to that of a symbol table for a con-
ventional programming language, although it contains a wider range of
data than a typical symbol table.”
You can summarize what you’ve learned about stacks and stack frames
like this:
• Each JVM thread has a private stack, created at the same time as
the thread.
• A stack stores frames, also called stack frames.
• A stack frame is created every time a new method is called.
There are two important lines in this description that relate to recursive
algorithms:
From all of these discussions I hope you can see the potential problem
of recursive algorithms:
60 JVM Stacks, Stack Frames, and Stack Overflow Errors
• When a recursive function calls itself, information for the new in-
stance of the function is pushed onto the stack in the form of a
new stack frame.
• Each time the function calls itself, another copy of the function
information is pushed onto the stack. With each recursive call, a
new stack frame is added to the stack.
• As a result, more and more memory that is allocated to the stack
is consumed as the function recurses. If the sum function calls itself
a million times, a million stack frames are created.
• Because the JVM stack size is relatively small, it’s easy for a re-
cursive function to consume all of this memory, which results in a
StackOverflowError.
11
A Visual Look at Stacks and Frames
Before the sum function is initially called, the only thing on the call stack
is the application’s main method, as shown in Figure 11.1.
Figure 11.1: main is the only method on the call stack before sum is called.
Then main calls sum with List(1,2,3), which I show in Figure 11.2 with-
out the “List” to keep things simple.
The data that’s given to sum matches its second case expression, and in
my pseudocode, that expression evaluates to this:
return 1 + sum(2,3)
61
62 A Visual Look at Stacks and Frames
So now a new instance of sum is called with List(2,3), and the stack
looks as shown in Figure 11.3.
Inside this sum call, the second case expression is matched, and the right
side of the => symbol evaluates to this:
return 2 + sum(3)
At this point a new instance of sum is called with the input parameter
List(3), and the stack looks like Figure 11.4.
Once again the second case expression is matched, and the right side of
the => symbol evaluates to this:
return 3 + sum(Nil)
Finally, another instance of sum is called with the input parameter Nil
— also known as List() — and the stack now looks like Figure 11.5.
This time, when sum(Nil) is called, the first case expression is matched:
63
Figure 11.5: The fourth (and final) sum call is added to the stack.
That pattern match causes this sum instance to return 0, and when it
does, the call stack unwinds and the stack frames are popped off of the
stack, as shown in the series of images in Figure 11.6.
Figure 11.6: In the unwinding of the call stack, the stack frames are popped off the
stack as each function yields its result.
In this process, as each sum call returns its result, its frame is popped off
of the stack, and when the recursion completely ends, the main method is
the only frame left on the call stack. (The value 6 is also returned by the
first sum invocation to the place where it was called in the main method.)
65
I hope that gives you a good idea of how recursive function calls are
pushed-on and popped-off the JVM call stack.
If you want to explore this in code, you can also see the series of sum
stack calls by modifying the sum function. To do this, add the lines of
code shown to the Nil case to print out stack trace information when
that case is reached:
java.base/java.lang.Thread.getStackTrace(Thread.java:1602)
rs$line$8$.sum(rs$line$8:5)
rs$line$8$.sum(rs$line$8:10)
rs$line$8$.sum(rs$line$8:10)
rs$line$8$.sum(rs$line$8:10)
rs$line$8$.sum(rs$line$8:10)
While that output isn’t too exciting, it shows that when the stack dump
66 A Visual Look at Stacks and Frames
is manually triggered when the Nil case is reached, the sum function is
on the stack five times. You can verify that this is correct by repeating
the test with a List that has three elements, in which case you’ll see the
sum function referenced only three times in the output:
java.base/java.lang.Thread.getStackTrace(Thread.java:1602)
rs$line$8$.sum(rs$line$8:5)
rs$line$8$.sum(rs$line$8:10)
rs$line$8$.sum(rs$line$8:10)
Clearly the sum function is being added to the stack over and over again,
once for each call.
I hope this little dive into the JVM stack and stack frames helps to explain
our current problem with “basic recursion.” As mentioned, if I try to
pass a List with 10,000 elements into the current recursive sum function,
it will generate a StackOverflowError. Because we’re trying to write
bulletproof programs, this isn’t good.
What’s next
Now that we looked at (a) basic recursion with the sum function, (b) how
that works with stacks and stack frames in the last two lessons, and (c)
how basic recursion can throw a StackOverflowError with large data
sets, the next lesson shows how to fix these problems with something
called “tail recursion.”
67
See also
One More Thing: Viewing and Setting the JVM Stack Size
“Well,” you say, “these days computers have crazy amounts of memory.
Why is this such a problem?”
According to this Oracle document, with Java 6 the default stack size was
very low: 1,024k on both Linux and Windows.
I encourage you to check the JVM stack size on your favorite computing
platform(s). One way to check it is with a command like this on a Unix-
based system:
It’s important to know that you can also control the JVM stack size with
the -Xss command line option:
That command sets the stack size to one megabyte. You specify the mem-
68 A Visual Look at Stacks and Frames
The Xss option can help if you run into a StackOverflowError, BUT, the
next lesson on tail recursion is intended to help you from ever needing this
command line option.
As a final note, you can find more options for controlling Java application
memory use by looking at the output of the java -X command:
$ java -X
If you dig through the output of that command, you’ll find that the
command-line arguments specifically related to Java application memory
use are:
You can use these parameters on the java command line like this:
-Xms64m or -Xms64M
-Xmx1g or -Xmx1G
70 A Visual Look at Stacks and Frames
12
Tail‐Recursive Algorithms
Goal
The goal of this lesson is to solve the problem shown in the previ-
ous lessons: Simple recursion creates a series of stack frames, and
for algorithms that require deep levels of recursion, this creates a
StackOverflowError (and crashes your program).
Although the previous lesson showed that algorithms with deep levels
of recursion can crash with a StackOverflowError, all is not lost. With
Scala you can work around this problem by making sure that your re-
cursive functions are written in a tail-recursive style.
A tail-recursive function is just a function whose very last action is a call to itself.
When you write your recursive function in this way, the Scala compiler
can optimize the resulting JVM bytecode so that the function requires only one
stack frame — as opposed to one stack frame for each level of recursion!
On this Stack Overflow page, Martin Odersky (creator of the Scala lan-
guage) explains tail-recursion in Scala:
71
72 Tail-Recursive Algorithms
“Hmm,” you might say, “if I understand Mr. Odersky’s quote, the sum
function you wrote at the end of the last lesson sure looks tail-recursive
to me”:
If that’s what you’re thinking, fear not, that’s an easy mistake to make —
and I should know, because that’s what I thought!
1. Call sum(xs)
2. After that function call returns, add its value to x and return that
result
When I make that code more explicit and write it as a series of one-line
expressions, you see that it looks like this:
73
case x :: xs =>
val s = sum(xs)
val result = x + s
return result
As shown, the last calculation that happens before the return statement is
that the sum of x and s is calculated. If you’re not 100% sure that you
believe that, there are a few ways you can prove it to yourself.
One way to “prove” that the sum algorithm is not tail-recursive is with
the stack trace output from the previous lesson. As you’ll recall, the JVM
output shows the sum method is called once for each step in the recursion
— five times when the list contains five elements — so it’s clear that the
JVM feels the need to create a new instance of sum for each element in
the collection.
For example, if you attempt to add the @tailrec annotation to sum, like
this:
@tailrec
74 Tail-Recursive Algorithms
the scalac compiler (or your IDE) will show an error message like this:
-- Error: --------------------------------------------------
4 | case x :: xs => x + sum(xs)
| ^^^^^^^
| Cannot rewrite recursive call: it is not
in tail position
1 error found
This is another way to “prove” that the Scala compiler doesn’t think sum
is tail-recursive.
Now that you know the current approach isn’t tail-recursive, the ques-
tion becomes, “How do I make it tail-recursive?”
1. Keep the original function signature the same (i.e., sum’s signa-
ture).
2. Create a second function by (a) copying the original function, (b)
giving it a new name, (c) making it private, (d) giving it a new
accumulator input parameter, and (e) adding the @tailrec anno-
tation to it.
3. Modify the second function’s algorithm so it uses the new accu-
mulator. (More on this shortly.)
4. Call the second function from inside the first function. When you
do this, you give the second function’s accumulator parameter a
75
“seed” value, such as the identity value I wrote about in the previous
lessons.
To begin the process of converting the recursive sum function into a tail-
recursive sum algorithm, leave the external signature of sum the same as it
was before:
Now create the second function by copying the first function, giving it a
new name, marking it private, giving it a new “accumulator” parameter
(named acc in this example), and adding the @tailrec annotation to it:
@tailrec
private def sumWithAccumulator(list: List[Int], acc: Int): Int =
list match
case Nil => 0
case x :: xs => x + sum(xs)
TIP: Another key thing to notice in this solution is that the data type for
the accumulator (Int) is the same as the data type held in the List that
we’re iterating over.
@tailrec
private def sumWithAccumulator(list: List[Int], acc: Int): Int =
list match
case Nil => acc
case x :: xs => sumWithAccumulator(xs, acc + x)
• The first parameter is the same list that the sum function receives.
• The second parameter is new. It’s the “accumulator” that I men-
tioned earlier.
• The inside of the sumWithAccumulator function looks similar. It
uses the same match/case approach that the original sum method
used.
• Rather than returning 0, the first case statement returns the
77
The result of this approach is that the “last action” of the sumWithAccumulator
function is this call:
sumWithAccumulator(xs, accumulator + x)
Because this last action really is a call back to the same function, the
JVM can optimize this code as Mr. Odersky described earlier.
The fourth step in the process is to modify the original function to call
the new function. Here’s the source code for the new version of sum:
Note that this seed value is the same as the identity value I wrote about in
the previous recursion lessons. In those lessons I noted:
When doing this, the thought process is, “Don’t expose the
79
When you make this change, the final code looks like this:
/**
* A tail-recursive solution with the accumulator function
* enclosed inside the outer `sum` function.
*/
import scala.annotation.tailrec
Feel free to use either approach. (Don’t tell anyone, but I prefer the first
approach; I think it reads more easily.)
/**
* A complete tail-recursive solution that shows a
* different name for the accumulator parameter.
*/
80 Tail-Recursive Algorithms
import scala.annotation.tailrec
@tailrec
def sumWithAccumulator(
list: List[Int],
runningTotal: Int // the ‘accumulator’ parameter
): Int = list match
case Nil => runningTotal
case x :: xs => sumWithAccumulator(xs, runningTotal + x)
To wrap up this discussion, let’s take a few moments to prove that the
compiler thinks this code is tail-recursive.
First proof
As you may be thinking, the first proof is already in the code. When you
compile this code with the @tailrec annotation and the compiler doesn’t
complain, you know that the compiler believes the code is tail-recursive.
81
Second proof
If for some reason you don’t believe the compiler, a second way to prove
this is to add some debug code to the new sum function, just like we did in
the previous lessons. Here’s the source code for a full Scala 3 application
that shows this approach:
import scala.annotation.tailrec
@tailrec
def sumWithAccumulator(
list: List[Int],
runningTotal: Int // the ‘accumulator’ parameter
): Int = list match
case Nil =>
val stackTraceAsArray = Thread.currentThread.getStackTrace
stackTraceAsArray.foreach(println)
runningTotal
case x :: xs =>
sumWithAccumulator(xs, runningTotal + x)
$ scala-cli SecondProof.scala
// ... other compiler output here ...
java.base/java.lang.Thread.getStackTrace(Thread.java:1610)
SecondProof$package$.sumWithAccumulator(SecondProof.scala:16)
SecondProof$package$.sum(SecondProof.scala:8)
SecondProof$package$.SumTailRecursive(SecondProof.scala:23)
SumTailRecursive.main(SecondProof.scala:22)
49995000
As you can see, although the List in the code contains 10,000 elements,
there’s only one call to sum, and more importantly in this case, only one
call to sumAccumulator. You can now safely call sum with even more
elements and it will work just fine without blowing the stack. (Go ahead
and test it!)
Key points
In this lesson I:
• Showed why the sum function I created in the previous lessons isn’t
tail-recursive
• Defined tail recursion
• Introduced the @tailrec annotation
• Showed how to write a tail-recursive function
• Showed a formula you can use to convert a simple recursive func-
tion to a tail-recursive function
• Showed ways to prove to yourself that a function is tail-recursive
83
TIP: Personally, I’m usually not smart enough to write a tail-recursive func-
tion right away, so I usually write my algorithms using simple recursion,
and then convert them to use tail-recursion.
84 Tail-Recursive Algorithms
13
Bonus: Processing I/O with Recursion
Because most of this text comes from that book, you’ll see that I occasion-
ally refer to the code we write as being like algebra. In FP, that’s the exact
mindset: When we write FP code, it’s just like we are mathematicians,
and every pure function we write is like an equation, and then we com-
bine those equations together in a series of expressions. The use of pure
functions, immutable data, and immutable variables (algebraic variables)
is what makes our code like algebra.
a
https://alvinalexander.com/scala/learn-functional-programming-book
Now that you’ve seen a lot of lessons that show how to use recursion to
iterate over the elements in a List, let’s look at another use of recursion:
looping over a data source that is not a List.
By the end of the lesson you’ll see how to write recursive code to create
85
86 Bonus: Processing I/O with Recursion
$ scala-cli MainLoopExample.scala
To do this, I’m going to combine recursion along with (a) a for expres-
sion and (b) the Scala Try data type, so I’ll briefly review those before
we get into the recursion.
’for’ expressions
A for expression begins with the for keyword, iterates over a data source
(or stream), lets you process that data as desired, and then ends by yield-
ing a result. It has the following general syntax, including the for and
yield keywords:
val result =
for
x <- xs // 'xs' is a source of data
y <- customFunction1(x)
z <- customFunction2(y)
yield
// add business logic here as desired
z
use the list xs as the data source for a for expression. This expression
yields a new list ys, where each integer in ys is twice the value of the
corresponding element in xs:
val xs = List(1, 2, 3)
val ys: List[Int] =
for
x <- xs
yield
x * 2
I wrote that for expression in a long form to clearly show the for/yield
sections, but you can also write it as a one-liner like this:
In summary, a for expression is used to (a) loop over a list (or other data
source), (b) process that data as desired, and (c) yield some result.
TIP: If you’re familiar with the map method on Scala collections classes,
the for expression just shown works the same as this map method:
A key thing to know is that Try is an error-handling data type. This means
88 Bonus: Processing I/O with Recursion
that rather than writing a function that throws an exception, you return
one of Try’s two sub-types, Success and Failure:
If you’re familiar with Java’s Optional data type, Try is similar to that,
but a significant difference is that Try gives us access to the exception
information. Because of that, I use it all the time for I/O functions and
any other function that can throw an exception. (That way I can tell the
end user what went wrong.)
A Try example
To demonstrate Try, imagine that you want to write a pure function that
converts a String to an Int. Because this function can receive bad input
like "foo" or "yo" as well as good input like "1" or "2", a pure function
must account for that bad input.
catch
case e: NumberFormatException => Failure(e)
This is what makeInt looks like when it’s called with both good and bad
data:
// success case
makeInt("1") // Success(1)
// failure case
makeInt("one") // Failure(java.lang.NumberFormatException:
// For input string: "one")
TIP 1: When you declare that a function returns a Try[Int], this means
that the Success value must contain an Int (while the Failure always
contains an exception). And, as described, a function either returns a
Success or a Failure.
TIP 2: Once you have a Try value — such as a result from makeInt — a
typical way to handle it is with a match expression:
90 Bonus: Processing I/O with Recursion
makeInt(aString) match
case Success(i) =>
println(s"Success: i = $i")
case Failure(e) =>
println(s"Failed: msg = ${e.getMessage}")
As a last point about Try, in a “basic” scenario like makeInt where you
have (a) an algorithm that throws an exception, and (b) you don’t want
to do anything special inside the try and catch blocks, you can write
your code more concisely like this:
In this situation, Try’s constructor works just like the longer code previ-
ously shown.
Before we start on the “looping” part of the code, we’ll first need two
functions, one to prompt a user for their input, and a second to read
91
their input:
import scala.io.StdIn
import scala.util.{Try, Success, Failure}
I use Try with these functions because I want you to imagine that these
functions are REST API calls, where one writes to a REST endpoint
and the other reads a REST response. Because network calls can fail,
you need to handle that possibility, and Try is perfect for this.
There’s a little bit of a “chicken and the egg” thing going on here, so
what I’m going to do is demonstrate a solution I know based on past
experience, and then I’ll explain that as I go on. Therefore, if you’ll
bear with me for a few moments, I’m going to start writing this code to
create a “main loop”:
• Inside for I’m going to prompt the user, then read and process
92 Bonus: Processing I/O with Recursion
their input
Given that background, let’s focus on writing the code inside the for
expression. In here we know that the first thing we want to do is prompt
the user for their input, so I add this line:
This code is a little different than the for expression I showed earlier, but
it can be read as, “Prompt the user for their input. Because printOutput
returns a Unit value wrapped inside a Try, I don’t care about that value,
so ignore it.” The key here is that I use the _ character on the left
side of the <- operator to say, “I don’t care about the value returned
by printOutput.”
I could also write that code with a variable name, like this:
but once you get used to seeing the _ character, it jumps out at you and
makes the intention to “ignore this value” very obvious.
93
The next thing I want to do inside the loop is read the user’s input, so I
add in a readInput call:
This new line of code reads the user input and binds it to the variable
named input. What happens here is that whatever the user types in
before they press the [Enter] key becomes a String that’s assigned to
the input variable.
For the purposes of this discussion I don’t care about potential errors, so
we’ll press on.
For MANY more details about how for expressions work, see my book,
Functional Programming, Simplifieda .
a
https://alvinalexander.com/scala/functional-programming-simplified-book
94 Bonus: Processing I/O with Recursion
Next, our application needs to process the user’s input. In a larger appli-
cation you’d write a function to do this, but to keep things simple I’m
just going to convert the user’s input to uppercase. Therefore, I know
that I want to do something like this:
Another important note is that the yield portion of the code returns the
symbol (). This is the Scala way to say that this code yields an instance
of the Unit type. Unit is like void or Void in other languages, and when
you return it from a yield, it means that this expression does not return
anything (or at least nothing of interest). Because I knew this was coming,
that’s how I knew mainLoop’s return type would be Try[Unit]:
95
At this point, mainLoop works as-is and will prompt a user one time, but
since I want to prompt them over and over again, we need to add some-
thing else …
When you’re working with immutable values, the solution to this prob-
lem is almost always the same: recursion. Therefore, you may not know
it yet, but what we need to do inside the for expression is to call mainLoop
recursively, so it starts the prompt/read/handleInput process all over
again.
if
input.toUpperCase == "Q" then System.exit(0)
else
mainLoop() // the recursive call
With a few minor adjustments, I then add that code to the existing for
expression:
You can format that code in different ways, but the important thing is
that this code:
_ <- {
val ucInput = input.toUpperCase
printOutput(ucInput + "\n")
if ucInput == "Q" then System.exit(0)
mainLoop()
}
• Inside that block, first convert the user’s input to uppercase, and
then print that value (ucInput)
• Next, if the uppercase version of their input is the string "Q", exit
the application
Therefore, when you don’t get "Q" as your input, mainLoop is called again,
it starts its for expression, which prompts the user (again) for their input.
97
A common pattern
As time goes on I’m sure that developers will find other ways to encapsu-
late this pattern (and some may exist already that I’m not aware of), but
until then, I wanted to let you know that in FP this is currently a very
common approach.
A complete application
Lastly, I’ll create a complete Scala 3 application to show that this works.
The only new thing I’ll add here is a Scala 3 main method, which is what
kicks off a Scala 3 application. As you’ll see in the code, all that main
method does is make an initial call to mainLoop to get the ball rolling:
import scala.io.StdIn
import scala.util.{Try, Success, Failure}
@main
def MainLoopExample =
// this starts the application running:
mainLoop()
$ scala-cli MainLoopExample.scala
Key points
This was a relatively large lesson, so let’s recap the key points:
One thing I glossed over in this lesson is that Try can be used inside
for expressions. I didn’t get into that here because it’s a long story, but
a very short answer is that Try works because it implements map and
flatMap methods, and any class that properly implements those methods
can work in for expressions. For much more detail on this, see my “Big
FP Book,” Functional Programming, Simplified1 .
1
https://alvinalexander.com/scala/functional-programming-simplified-book
14
The End
Other books
If you’re interested in other books I’ve written on Scala, here’s the cur-
rent list as of January, 2023:
1
https://alvinalexander.com/scala/functional-programming-simplified-book-scala-3
2
https://amzn.to/3du1pMR
101
102 The End
3
https://alvinalexander.com/scala/functional-programming-simplified-book
4
https://alvinalexander.com//scala/learn-functional-programming-book
5
https://alvinalexander.com/scala/learn-scala-3-the-fast-way-book
103
Other resources
Support my writing
As a last note, if you’d like to see more free documents like this in the
future (and free videos), you can support my work here:
6
https://alvinalexander.com/source-code/scala-foldleft-function-using-recursion/
7
https://alvinalexander.com/scala/scala-recursion-examples-recursive-programming
8
https://alvinalexander.com/scala/scala-factorial-recursion-example-recursive-programming/
9
https://alvinalexander.com/scala/fp-book/recursion-great-but-fold-reduce-in-scala/
10
https://alvinalexander.com/scala/fp-book/quick-review-scala-for-expressions/
11
https://www.youtube.com/@devdaily/videos
12
https://ko-fi.com/alvin
13
https://www.patreon.com/alvinalexander
104 The End
Find me here
• alvinalexander.com14
• twitter.com/alvinalexander15
• linkedin.com/in/alvinalexander16
14
https://alvinalexander.com
15
https://twitter.com/alvinalexander
16
https://www.linkedin.com/in/alvinalexander
Index
FPer, 8
getStackTrace, 65
JVM
stack, 54
stack frame, 56
linked list
cons cells, 10
list
head, 11
tail, 11
lists
end with Nil, 25
visualizing, 9
ways to create, 14
Martin Odersky, 71
recursion
accumulator, 74
case statements, 20
conversation, 39
how unwinding works, 27
stack and stack frames, 58
sum function, 17
thought process, 43
unwinding, 25, 64
visualizing, 31
recursion, tail, 71
stack, 61
105