Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 70 additions & 42 deletions exercises/intro/README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
Introduction to Functional Programming in Scala
Introduction to Functional Programming (FP) in Scala
============

In this tutorial we will start with a trivial program, written in imperative
style, and through a series of refactorings transform it into a functional
program while learning the core FP concepts like referential transparency,
expression-based programming, recursion, immutability and higher-order
functions.
program while learning core FP concepts such as:

* referential transparency,
* expression-based programming,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Breaking down long sentences in bullet points is good 👍

* recursion,
* immutability, and
* higher-order functions.

Getting Started
---------------

Open file `src/main/scala/Main.scala` in your editor of choice. To run the
program start `sbt` from the terminal and type `run` in SBT prompt.
Open file `src/main/scala/Main.scala` in your editor of choice.
To run the program start `sbt` from the terminal and type `run` in SBT prompt.

The Challenge
-------------
Expand All @@ -31,7 +35,7 @@ The "Real" Program
------------------

We'll start with almost a schoolbook solution in Java that will have loops,
conditionals and variables. This first example is in Java instead of Scala
conditionals, and variables. This first example is in Java instead of Scala
so that it's immediately familiar to anyone who did some programming in
procedural imperative language before:

Expand All @@ -57,16 +61,16 @@ public class Loop {
</center>
<br>

This program is, on purpose, easy enough to understand. Consider however, that
in your real programming work you might deal with several complexities at the same
time and thus a simple `for` loop with a variable can quickly get out of hand:

This program is on purpose easy enough to understand. Consider however that
in your real programming work you would iterate over some complex domain
objects instead on numbers, maybe over two or three arrays of those
simultaneously, there would be many more conditions and they would be nested,
there would be multiple variables to keep track of running totals and they
would all change independently of each other based on complex conditional
logic. And what about running this loop on multiple CPU cores in parallel so
that it can finish faster? You see how a simple `for` loop with a variable can
quickly get out of hand?
* iterating over some complex domain objects instead on numbers,
* iterating over two or three arrays of those domain objects simultaneously,
* multiple and / or nested conditions,
* multiple variables to keep track of running totals,
* multiple variables that all change independently based on complex conditional logic, and
* parallel loop execution (i.e. utilising multiple CPU cores).

Two cornerstones of imperative programming languages contribute to the
problem here:
Expand Down Expand Up @@ -119,13 +123,13 @@ First you notice that Scala is less verbose: no semicolons needed, no `public

1. `object` keyword defines a singleton value initialized by the block of code
inside it. Extending `App` makes it an entrypoint of an application;
2. `var` keyword defines a variable that can be reassigned later. Type of the
1. `var` keyword defines a variable that can be reassigned later. Type of the
variable is inferred from the right-hand side of the assignment, `Int` in
this case;
3. `1 to 10` defines a inclusive `Range` – a sequence that can be enumerated;
4. `for (x <- seq) { ...x... }` enumerates elements of `seq` executing the
1. `1 to 10` defines a inclusive `Range` – a sequence that can be enumerated;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know that MD renderer will turn this into a numbered list, but I prefer the text itself to be readable too (that was the selling point of MD, IIRC). A numbered list with ones somewhat defeats that purpose.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, will revert.

1. `for (x <- seq) { ...x... }` enumerates elements of `seq` executing the
code block in curly braces with `x` representing each element in scope;
5. `s"...$x..."` does string interpolation of value `x`.
1. `s"...$x..."` does string interpolation of value `x`.

Decomplecting Functions
-----------------------
Expand All @@ -144,6 +148,27 @@ for (x <- 1 to 10) { // iterate
There are 4 distinct functions that are entangled in this statement-based
program. No single function can be tested in isolation. Let's pull them apart:

```scala
def iterate(max: Int): List[Int] = ???
def filterEven(xs: List[Int]): List[Int] = ???
def square(xs: List[Int]): List[Int] = ???
def sum(xs: List[Int]): Int = ???
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good trick 👍 I didn't think about using ??? to show the intermediary step here.


val result = sum(square(filterEven(iterate(10))))
```

Let's review the function and variable assignment Scala syntax first:

1. `def f(x: Int): List[Int]` defines function `f` that takes an
argument `x` of type `Int` and returns a `List` of `Int`s;
1. `???` is a way to define a method stub (like a TODO),
which will allow the program to compile but throws `NotImplementedError`
when the function is called;
1. `val x = ...` defines an immutable value, once assigned the value
cannot be changed (unlike a `var`).

So how would the function implementation look?

```scala
def iterate(max: Int): List[Int] = {
var result = List[Int]()
Expand Down Expand Up @@ -193,14 +218,12 @@ val result = sum(square(filterEven(iterate(10))))
<br>

Wow! The number of lines of code just exploded and we introduced a lot of
duplication along the way. Let's review new Scala syntax first:
duplication along the way. Let's review the function body implementation:

1. `def f(x: Int): List[Int] = {...}` defines function `f` that takes an
argument `x` of type `Int` and returns a `List` of `Int`s;
2. The last expression of a function body is its return value;
3. `List[Int]()` creates an empty list of integers;
4. `list :+ element` returns a new list made of appending `element` to `list`;
5. `val x = ...` defines an immutable value that cannot be changed.
1. The last expression of a function body is its return value;
1. `List[Int]()` creates an empty list of integers, one alternative is to use
the equivalent factory method `List.empty[Int]` (a matter of preference);
1. `list :+ element` returns a new list made of appending `element` to `list`;

Despite explosion of code and duplication we've made our program
composable: every function can be tested in isolation and functions can be
Expand All @@ -212,15 +235,18 @@ universal building blocks.

Consider the expression `sum(square(filterEven(iterate(10))))`. Unlike a
program made of statements, every sub-expression is an expression on its own:
`10` is an expression which evaluates to 10, `iterate(10)` is an expression
which evaluates to `List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)`, `filterEven
(iterate(10))` is an expression which evaluates to `List(2, 4, 6, 8, 10)`
and so on. Every expression can be replaced with the value it evaluates to as

* `10` is an expression which evaluates to 10,
* `iterate(10)` is an expression which evaluates to `List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)`,
* `filterEven (iterate(10))` is an expression which evaluates to `List(2, 4, 6, 8, 10)`,
* and so on.

Every expression can be replaced with the value it evaluates to as
long as functions involved in the expression don't have side effects – e.g.
launch the proverbial missiles or modify variables in other parts of the
program. In functional programming such functions are called *pure* and the
property allowing substitution of values for expressions is called
*referential transparency* meaning that referring to a value via an
*referential transparency*, meaning that referring to a value via an
indirection of a function call is transparent and doesn't change program
execution.

Expand Down Expand Up @@ -273,14 +299,14 @@ First, let's clear the new Scala syntax out of the way:

1. Functions can be defined inside the body of other functions, this is not
recursion though;
2. `@tailrec` annotation checks at compile time that our recursive function
1. `@tailrec` annotation checks at compile time that our recursive function
`loop` will not blow the call stack with large inputs;
3. We use *pattern matching* to match on integer `max`. It looks like `switch`
1. We use *pattern matching* to match on integer `max`. It looks like `switch`
statement in other programming languages, but is an expression instead of a
statement because every `case` branch has to evaluate to the same type;
4. `case _` matches any value;
5. `element :: list` creates a new list with `element` prepended to `list`;
6. `Nil` is an empty list.
1. `case _` matches any value (i.e. catchall);
1. `element :: list` creates a new list with `element` prepended to `list`;
1. `Nil` is an empty list.

Recursive pattern is to pass the intermediary result along with the reduced
input to the `loop` function itself. The recursive function breaks out of
Expand Down Expand Up @@ -497,15 +523,17 @@ def sumOfEvenSquares(max: Int): Int = {
</center>
<br>

Here `square` really does only what it says and doesn't mess with lists
anymore, that's responsibility of `map` now.
Here, `square` really does only what it says and doesn't mess with lists
anymore, which is now the responsibility of `map`.

Collection API
--------------

By a lucky coincidence Scala standard library collection API already provides
most of the functions that we've just discovered. It would be a shame not to
use them:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This supposed to be a joke 🤣

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wooosh 😅 I'll add air quotes, haha.

We have gone to some length to describe some basic collection operations such
as `fold` and `map`. Because these operations are very commonly used, Scala
provided them in the standard library as part of the collections API. So let's
perform one final refactoring to swap our own implementation with ones from the
standard library:

```scala
def isEven(x: Int): Boolean = x % 2 == 0
Expand Down