-
Notifications
You must be signed in to change notification settings - Fork 0
Chore - Intro code examples #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
c254c98
a960e27
4220c83
fbfb76f
a577375
375b0cf
8017189
37e0f47
b0d1290
9803b09
f70c2f7
9e8a620
e776285
74e41e2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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, | ||
| * 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 | ||
| ------------- | ||
|
|
@@ -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: | ||
|
|
||
|
|
@@ -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: | ||
|
|
@@ -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; | ||
|
||
| 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 | ||
| ----------------------- | ||
|
|
@@ -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 = ??? | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good trick 👍 I didn't think about using |
||
|
|
||
| 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]() | ||
|
|
@@ -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 | ||
|
|
@@ -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. | ||
|
|
||
|
|
@@ -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 | ||
|
|
@@ -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: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This supposed to be a joke 🤣 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
|
||
There was a problem hiding this comment.
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 👍