(download for perfect quality) See aggregation functions defined inductively and implemented using recursion.
Learn how in many cases, tail-recursion and the accumulator trick can be used to avoid stack-overflow errors.
Watch as general aggregation is implemented and see duality theorems capturing the relationship between left folds and right folds.
Through the work of Sergei Winitzki and Richard Bird.
Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala Part 2 ...Philip Schwarz
(download for perfect quality) See aggregation functions defined inductively and implemented using recursion.
Learn how in many cases, tail-recursion and the accumulator trick can be used to avoid stack-overflow errors.
Watch as general aggregation is implemented and see duality theorems capturing the relationship between left folds and right folds.
Through the work of Sergei Winitzki and Richard Bird.
This version corrects the following issues:
slide 32: = reverse --> reverse =
Slide 33: 100_000 -> 1_000_000
It also adds slides 36, 37 and 38
Game of Life - Polyglot FP - Haskell - Scala - Unison - Part 3Philip Schwarz
(download for picture-perfect quality) Follow along as Trampolining is used to overcome Stack Overflow issues with the simple IO monad, deepening you understanding of the IO monad in the process. See Game of Life IO actions migrated to the Cats Effect IO monad, which is trampolined in its flatMap evaluation.
Errata:
slide 33: "impure factorial function" should be "initial factorial function"
slide 34: there is a pointless short vertical bar inside a speech bubble
slide 39: "it is till annoying" should be "it is still annoying"
slide 44: "values that we are interested" should be "values that we are interested in"
Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala - Part 4Philip Schwarz
(download for flawless image quality) Can a left fold ever work over an infinite list? What about a right fold? Find out.
Learn about the other two functions used by functional programmers to implement mathematical induction: iterating and scanning.
Learn about the limitations of the accumulator technique and about tupling, a technique that is the dual of the accumulator trick.
The Functional Programming Triad of Folding, Scanning and Iteration - a first...Philip Schwarz
This slide deck can work both as an aide mémoire (memory jogger), or as a first (not completely trivial) example of using left folds, left scans and iteration, to implement mathematical induction.
Errata: on almost half of the slides there is some minor typo or imperfection or in some cases a minor error and in one case, an omission. See a later version for corrections and some improvements.
(for best quality images, either download or view here: https://philipschwarz.dev/fpilluminated/?page_id=455)
Scala code for latest version: https://github.com/philipschwarz/fp-fold-scan-iterate-triad-a-first-example-scala
Function Applicative for Great Good of Palindrome Checker Function - Polyglot...Philip Schwarz
Embark on an informative and fun journey through everything you need to know to understand how the Applicative instance for functions makes for a terse palindrome checker function definition in point-free style.
N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...Philip Schwarz
See how the guard function has migrated from MonadPlus to Alternative and learn something about the latter.
Learn how to write a Scala program that draws an N-Queens solution board using the Doodle graphics library.
See how to write the equivalent Haskell program using the Gloss graphics library.
Learn how to use Monoid and Foldable to compose images both in Haskell and in Scala.
Link to part 1: https://www.slideshare.net/pjschwarz/nqueens-combinatorial-problem-polyglot-fp-for-fun-and-profit-haskell-and-scala-part-1
Errata:
On slide 22, the last line of the showQueens function should of course be show(solution).draw(frame) rather than show(solution).draw
On slide 43, it would be better if the definitions of the beside, above and on Monoids were also shown.
Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala - Part ...Philip Schwarz
(download for flawless image quality) Can a left fold ever work over an infinite list? What about a right fold? Find out.
Learn about the other two functions used by functional programmers to implement mathematical induction: iterating and scanning.
Learn about the limitations of the accumulator technique and about tupling, a technique that is the dual of the accumulator trick.
Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala Part 2 ...Philip Schwarz
(download for perfect quality) See aggregation functions defined inductively and implemented using recursion.
Learn how in many cases, tail-recursion and the accumulator trick can be used to avoid stack-overflow errors.
Watch as general aggregation is implemented and see duality theorems capturing the relationship between left folds and right folds.
Through the work of Sergei Winitzki and Richard Bird.
This version corrects the following issues:
slide 32: = reverse --> reverse =
Slide 33: 100_000 -> 1_000_000
It also adds slides 36, 37 and 38
Game of Life - Polyglot FP - Haskell - Scala - Unison - Part 3Philip Schwarz
(download for picture-perfect quality) Follow along as Trampolining is used to overcome Stack Overflow issues with the simple IO monad, deepening you understanding of the IO monad in the process. See Game of Life IO actions migrated to the Cats Effect IO monad, which is trampolined in its flatMap evaluation.
Errata:
slide 33: "impure factorial function" should be "initial factorial function"
slide 34: there is a pointless short vertical bar inside a speech bubble
slide 39: "it is till annoying" should be "it is still annoying"
slide 44: "values that we are interested" should be "values that we are interested in"
Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala - Part 4Philip Schwarz
(download for flawless image quality) Can a left fold ever work over an infinite list? What about a right fold? Find out.
Learn about the other two functions used by functional programmers to implement mathematical induction: iterating and scanning.
Learn about the limitations of the accumulator technique and about tupling, a technique that is the dual of the accumulator trick.
The Functional Programming Triad of Folding, Scanning and Iteration - a first...Philip Schwarz
This slide deck can work both as an aide mémoire (memory jogger), or as a first (not completely trivial) example of using left folds, left scans and iteration, to implement mathematical induction.
Errata: on almost half of the slides there is some minor typo or imperfection or in some cases a minor error and in one case, an omission. See a later version for corrections and some improvements.
(for best quality images, either download or view here: https://philipschwarz.dev/fpilluminated/?page_id=455)
Scala code for latest version: https://github.com/philipschwarz/fp-fold-scan-iterate-triad-a-first-example-scala
Function Applicative for Great Good of Palindrome Checker Function - Polyglot...Philip Schwarz
Embark on an informative and fun journey through everything you need to know to understand how the Applicative instance for functions makes for a terse palindrome checker function definition in point-free style.
N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...Philip Schwarz
See how the guard function has migrated from MonadPlus to Alternative and learn something about the latter.
Learn how to write a Scala program that draws an N-Queens solution board using the Doodle graphics library.
See how to write the equivalent Haskell program using the Gloss graphics library.
Learn how to use Monoid and Foldable to compose images both in Haskell and in Scala.
Link to part 1: https://www.slideshare.net/pjschwarz/nqueens-combinatorial-problem-polyglot-fp-for-fun-and-profit-haskell-and-scala-part-1
Errata:
On slide 22, the last line of the showQueens function should of course be show(solution).draw(frame) rather than show(solution).draw
On slide 43, it would be better if the definitions of the beside, above and on Monoids were also shown.
Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala - Part ...Philip Schwarz
(download for flawless image quality) Can a left fold ever work over an infinite list? What about a right fold? Find out.
Learn about the other two functions used by functional programmers to implement mathematical induction: iterating and scanning.
Learn about the limitations of the accumulator technique and about tupling, a technique that is the dual of the accumulator trick.
Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala - Part 5Philip Schwarz
(download for best quality slides) Gain a deeper understanding of why right folds over very large and infinite lists are sometimes possible in Haskell.
See how lazy evaluation and function strictness affect left and right folds in Haskell.
Learn when an ordinary left fold results in a space leak and how to avoid it using a strict left fold.
Errata:
slide 15: "as sharing is required" should be "as sharing is not required"
slide 43: 𝑠𝑓𝑜𝑙𝑑𝑙 (⊕) 𝑎 should be 𝑠𝑓𝑜𝑙𝑑𝑙 (⊕) 𝑒
Download for flawless quality (slides viewed online look a bit grainy and out of focus). A monad is an implementation of one of the minimal sets of monadic combinators, satisfying the laws of associativity and identity - see how compositional responsibilities are distributed in each combinator set
Sierpinski Triangle - Polyglot FP for Fun and Profit - Haskell and ScalaPhilip Schwarz
Take the very first baby steps on the path to doing graphics in Haskell and Scala.
Learn about a simple yet educational recursive algorithm producing images that are pleasing to the eye.
Learn how functional programs deal with the side effects required to draw images.
See how libraries like Gloss and Doodle make drawing Sierpinski’s triangle a doddle.
Code for this slide deck:
https://github.com/philipschwarz/sierpinski-triangle-haskell-gloss
https://github.com/philipschwarz/sierpinski-triangle-scala-cats-io
https://github.com/philipschwarz/sierpinski-triangle-scala-awt-and-doodle
Errata:
1. the title 'Sierpinski Triangle' on the front slide could be improved by replacing it with 'Sierpinski's Triangle'.
2. a couple of typos on two slides
3. the triangles drawn using Doodle are not equilateral, as intended but isosceles.
(UPDATE 2021-06-15 I opened PR https://github.com/creativescala/doodle/pull/99 and as a result, an equilateral triangle has now been added to Doodle: https://github.com/creativescala/doodle/commit/30d20efebcc2016942e9cdbae85fefca5b95fa3c).
Here is a corrected version of the deck: https://www.slideshare.net/pjschwarz/sierpinski-triangle-polyglot-fp-for-fun-and-profit-haskell-and-scala-with-minor-corrections
The Functional Programming Triad of Map, Filter and FoldPhilip Schwarz
This slide deck is my homage to SICP, the book which first introduced me to the Functional Programming triad of map, filter and fold.
It was during my Computer Science degree that a fellow student gave me a copy of the first edition, not long after the book came out.
I have not yet come across a better introduction to these three functions.
The upcoming slides are closely based on the second edition of the book, a free online copy of which can be found here:
https://mitpress.mit.edu/sites/default/files/sicp/full-text/book/book.html.
Download for original image quality.
Errata:
slide 20: the Clojure map function is in fact the Scheme one repeated - see code below for correction.
Scheme code: https://github.com/philipschwarz/the-fp-triad-of-map-filter-and-fold-scheme
Clojure code: https://github.com/philipschwarz/the-fp-triad-of-map-filter-and-fold-clojure
Introducing Assignment invalidates the Substitution Model of Evaluation and v...Philip Schwarz
(download for better quality)
Introducing Assignment invalidates the Substitution Model of Evaluation and violates Referential Transparency
- as explained in SICP (the Wizard Book)
Game of Life - Polyglot FP - Haskell, Scala, Unison - Part 2 - with minor cor...Philip Schwarz
(download for picture-perfect quality) Follow along as the impure functions in the Game of Life are translated from Haskell into Scala, deepening you understanding of the IO monad in the process.
This is simply a copy of the original with an error corrected on slides 3 and 52, which were supposed to show Scala code and instead showed the Haskell equivalent! Plus removal of a few minor aesthetic imperfections.
Original: https://www.slideshare.net/pjschwarz/game-of-life-polyglot-fp-haskell-scala-unison-part-2
Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala - Part ...Philip Schwarz
(download for best quality slides) Gain a deeper understanding of why right folds over very large and infinite lists are sometimes possible in Haskell.
See how lazy evaluation and function strictness affect left and right folds in Haskell.
Learn when an ordinary left fold results in a space leak and how to avoid it using a strict left fold.
This version eliminates some minor imperfections and corrects the following two errors:
slide 15: "as sharing is required" should be "as sharing is not required"
slide 43: 푠푓표푙푑푙 (⊕) 푎 should be 푠푓표푙푑푙 (⊕) 푒
Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala - with ...Philip Schwarz
(download for perfect quality) - See how recursive functions and structural induction relate to recursive datatypes.
Follow along as the fold abstraction is introduced and explained.
Watch as folding is used to simplify the definition of recursive functions over recursive datatypes
Part 1 - through the work of Richard Bird and Graham Hutton.
This version corrects the following issues:
slide 7, 11 fib(0) is 0,rather than 1
slide 23: was supposed to be followed by 2-3 slides recapitulating definitions of factorial and fibonacci with and without foldr, plus translation to scala
slide 36: concat not invoked in concat example
slides 48 and 49: unwanted 'm' in definition of sum
throughout: a couple of typographical errors
throughout: several aesthetic imperfections (wrong font, wrong font colour)
The Functional Programming Triad of Folding, Scanning and Iteration - a first...Philip Schwarz
This slide deck can work both as an aide mémoire (memory jogger), or as a first (not completely trivial) example of using left folds, left scans and iteration, to implement mathematical induction.
This is just an updated version of the original slide deck which makes minor improvements and minor corrections in almost half of the slides.
(for best quality images, either download or view here: https://philipschwarz.dev/fpilluminated/?page_id=455)
You can see the Scala code here: https://github.com/philipschwarz/fp-fold-scan-iterate-triad-a-first-example-scala
Scala 3 enum for a terser Option Monad Algebraic Data TypePhilip Schwarz
(download for flawless slides)
* Explore a terser definition of the Option Monad that uses a Scala 3 enum as an Algebraic Data Type.
* In the process, have a tiny bit of fun with Scala 3 enums.
* Get a refresher on the Functor and Monad laws.
* See how easy it is to use Scala 3 extension methods, e.g. to add convenience methods and infix operators.
The diagrams for function composition and Kleisli composition were made using https://q.uiver.app/ by https://twitter.com/varkora.
Source code: https://github.com/philipschwarz/scala-3-enum-for-terser-option-monad-algebraic-data-type
Errata:
slide 14 is an unwanted leftover - it is the same as slide 15 minus a diagram.
on slide 19, the colons in the extension method declarations are not needed
The Functional Programming Triad of fold, scan and iteratePhilip Schwarz
In the functional programming paradigm, the programmer does not need to write any loops or use array indices. Instead, functional programers deal with iterative calculations by translating mathematical induction directly into code, so that they can reason about sequences as mathematical values. The implement mathematical induction by folding, scanning and iterating.
Ouch - a couple of occurrences of 'mathematical' are misspelled (missing h) :-(
download for better quality - Learn how to use an Applicative Functor to handle multiple independent effectful values through the work of Sergei Winitzki, Runar Bjarnason, Paul Chiusano, Debasish Ghosh and Adelbert Chang
(download for flawless quality) State Monad - Learn how it works - Follow Alvin Alexander’s example-driven build up to the State Monad and then branch off into a detailed look at its inner workings.
Functional Core and Imperative Shell - Game of Life Example - Haskell and ScalaPhilip Schwarz
See a program structure flowchart used to highlight how an FP program breaks down into a functional core and imperative shell
View a program structure flowchart for the Game of Life
See the code for Game of Life’s functional core and imperative shell, both in Haskell and in Scala.
Code:
https://github.com/philipschwarz/functional-core-imperative-shell-scala
https://github.com/philipschwarz/functional-core-imperative-shell-haskell
Here I link up up some very useful material by Robert Norris (@tpolecat) and Martin Odersky (@odersky) to introduce Monad laws and reinforce the importance of checking the laws. E.g. while Option satisfies the laws, Try is not a lawful Monad: it trades the left identity law for the bullet-proof principle.
* ERRATA *
1) on the first slide the Kleisli composition signature appears three times, but one of the occurrences is incorrect in that it is (>=>)::(a->mb)->(b->mb)->(a->mc) whereas is should be (>=>)::(a->mb)->(b->mc)->(a->mc) - thank you Jules Ivanic.
From Scala Monadic Effects to Unison Algebraic EffectsPhilip Schwarz
Introduction to Unison’s algebraic effects (abilities) - go from a small Scala program based on the Option monad to a Unison program based on the Abort ability - inspired by, and part based on, a talk by Runar Bjarnason.
See here for the code:
https://github.com/philipschwarz/from-scala-monadic-effects-to-unison-algebraic-effects-scala-code
https://github.com/philipschwarz/from-scala-monadic-effects-to-unison-algebraic-effects-unison-code
N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit - Haskell and...Philip Schwarz
See how feeding FP workhorses map and filter with monadic steroids turns them into the intriguing mapM and filterM.
Graduate to foldM by learning how it behaves with the help of three simple yet instructive examples of its usage.
Use the powers of foldM to generate all permutations of a collection with a simple one-liner.
Exploit what you learned about foldM to solve the N-Queens Combinatorial Problem with an iterative approach rather than a recursive one.
Monad as functor with pair of natural transformationsPhilip Schwarz
Explains why a Monad is a functor with a pair of natural transformations (plus associativity and identity laws). It then explores this by looking at an example, with code in Scala.
Inspired and based on videos/publications by Bartosz Milewski, Rúnar Bjarnason and Rob Norris.
Download for better quality.
Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala - Part 5Philip Schwarz
(download for best quality slides) Gain a deeper understanding of why right folds over very large and infinite lists are sometimes possible in Haskell.
See how lazy evaluation and function strictness affect left and right folds in Haskell.
Learn when an ordinary left fold results in a space leak and how to avoid it using a strict left fold.
Errata:
slide 15: "as sharing is required" should be "as sharing is not required"
slide 43: 𝑠𝑓𝑜𝑙𝑑𝑙 (⊕) 𝑎 should be 𝑠𝑓𝑜𝑙𝑑𝑙 (⊕) 𝑒
Download for flawless quality (slides viewed online look a bit grainy and out of focus). A monad is an implementation of one of the minimal sets of monadic combinators, satisfying the laws of associativity and identity - see how compositional responsibilities are distributed in each combinator set
Sierpinski Triangle - Polyglot FP for Fun and Profit - Haskell and ScalaPhilip Schwarz
Take the very first baby steps on the path to doing graphics in Haskell and Scala.
Learn about a simple yet educational recursive algorithm producing images that are pleasing to the eye.
Learn how functional programs deal with the side effects required to draw images.
See how libraries like Gloss and Doodle make drawing Sierpinski’s triangle a doddle.
Code for this slide deck:
https://github.com/philipschwarz/sierpinski-triangle-haskell-gloss
https://github.com/philipschwarz/sierpinski-triangle-scala-cats-io
https://github.com/philipschwarz/sierpinski-triangle-scala-awt-and-doodle
Errata:
1. the title 'Sierpinski Triangle' on the front slide could be improved by replacing it with 'Sierpinski's Triangle'.
2. a couple of typos on two slides
3. the triangles drawn using Doodle are not equilateral, as intended but isosceles.
(UPDATE 2021-06-15 I opened PR https://github.com/creativescala/doodle/pull/99 and as a result, an equilateral triangle has now been added to Doodle: https://github.com/creativescala/doodle/commit/30d20efebcc2016942e9cdbae85fefca5b95fa3c).
Here is a corrected version of the deck: https://www.slideshare.net/pjschwarz/sierpinski-triangle-polyglot-fp-for-fun-and-profit-haskell-and-scala-with-minor-corrections
The Functional Programming Triad of Map, Filter and FoldPhilip Schwarz
This slide deck is my homage to SICP, the book which first introduced me to the Functional Programming triad of map, filter and fold.
It was during my Computer Science degree that a fellow student gave me a copy of the first edition, not long after the book came out.
I have not yet come across a better introduction to these three functions.
The upcoming slides are closely based on the second edition of the book, a free online copy of which can be found here:
https://mitpress.mit.edu/sites/default/files/sicp/full-text/book/book.html.
Download for original image quality.
Errata:
slide 20: the Clojure map function is in fact the Scheme one repeated - see code below for correction.
Scheme code: https://github.com/philipschwarz/the-fp-triad-of-map-filter-and-fold-scheme
Clojure code: https://github.com/philipschwarz/the-fp-triad-of-map-filter-and-fold-clojure
Introducing Assignment invalidates the Substitution Model of Evaluation and v...Philip Schwarz
(download for better quality)
Introducing Assignment invalidates the Substitution Model of Evaluation and violates Referential Transparency
- as explained in SICP (the Wizard Book)
Game of Life - Polyglot FP - Haskell, Scala, Unison - Part 2 - with minor cor...Philip Schwarz
(download for picture-perfect quality) Follow along as the impure functions in the Game of Life are translated from Haskell into Scala, deepening you understanding of the IO monad in the process.
This is simply a copy of the original with an error corrected on slides 3 and 52, which were supposed to show Scala code and instead showed the Haskell equivalent! Plus removal of a few minor aesthetic imperfections.
Original: https://www.slideshare.net/pjschwarz/game-of-life-polyglot-fp-haskell-scala-unison-part-2
Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala - Part ...Philip Schwarz
(download for best quality slides) Gain a deeper understanding of why right folds over very large and infinite lists are sometimes possible in Haskell.
See how lazy evaluation and function strictness affect left and right folds in Haskell.
Learn when an ordinary left fold results in a space leak and how to avoid it using a strict left fold.
This version eliminates some minor imperfections and corrects the following two errors:
slide 15: "as sharing is required" should be "as sharing is not required"
slide 43: 푠푓표푙푑푙 (⊕) 푎 should be 푠푓표푙푑푙 (⊕) 푒
Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala - with ...Philip Schwarz
(download for perfect quality) - See how recursive functions and structural induction relate to recursive datatypes.
Follow along as the fold abstraction is introduced and explained.
Watch as folding is used to simplify the definition of recursive functions over recursive datatypes
Part 1 - through the work of Richard Bird and Graham Hutton.
This version corrects the following issues:
slide 7, 11 fib(0) is 0,rather than 1
slide 23: was supposed to be followed by 2-3 slides recapitulating definitions of factorial and fibonacci with and without foldr, plus translation to scala
slide 36: concat not invoked in concat example
slides 48 and 49: unwanted 'm' in definition of sum
throughout: a couple of typographical errors
throughout: several aesthetic imperfections (wrong font, wrong font colour)
The Functional Programming Triad of Folding, Scanning and Iteration - a first...Philip Schwarz
This slide deck can work both as an aide mémoire (memory jogger), or as a first (not completely trivial) example of using left folds, left scans and iteration, to implement mathematical induction.
This is just an updated version of the original slide deck which makes minor improvements and minor corrections in almost half of the slides.
(for best quality images, either download or view here: https://philipschwarz.dev/fpilluminated/?page_id=455)
You can see the Scala code here: https://github.com/philipschwarz/fp-fold-scan-iterate-triad-a-first-example-scala
Scala 3 enum for a terser Option Monad Algebraic Data TypePhilip Schwarz
(download for flawless slides)
* Explore a terser definition of the Option Monad that uses a Scala 3 enum as an Algebraic Data Type.
* In the process, have a tiny bit of fun with Scala 3 enums.
* Get a refresher on the Functor and Monad laws.
* See how easy it is to use Scala 3 extension methods, e.g. to add convenience methods and infix operators.
The diagrams for function composition and Kleisli composition were made using https://q.uiver.app/ by https://twitter.com/varkora.
Source code: https://github.com/philipschwarz/scala-3-enum-for-terser-option-monad-algebraic-data-type
Errata:
slide 14 is an unwanted leftover - it is the same as slide 15 minus a diagram.
on slide 19, the colons in the extension method declarations are not needed
The Functional Programming Triad of fold, scan and iteratePhilip Schwarz
In the functional programming paradigm, the programmer does not need to write any loops or use array indices. Instead, functional programers deal with iterative calculations by translating mathematical induction directly into code, so that they can reason about sequences as mathematical values. The implement mathematical induction by folding, scanning and iterating.
Ouch - a couple of occurrences of 'mathematical' are misspelled (missing h) :-(
download for better quality - Learn how to use an Applicative Functor to handle multiple independent effectful values through the work of Sergei Winitzki, Runar Bjarnason, Paul Chiusano, Debasish Ghosh and Adelbert Chang
(download for flawless quality) State Monad - Learn how it works - Follow Alvin Alexander’s example-driven build up to the State Monad and then branch off into a detailed look at its inner workings.
Functional Core and Imperative Shell - Game of Life Example - Haskell and ScalaPhilip Schwarz
See a program structure flowchart used to highlight how an FP program breaks down into a functional core and imperative shell
View a program structure flowchart for the Game of Life
See the code for Game of Life’s functional core and imperative shell, both in Haskell and in Scala.
Code:
https://github.com/philipschwarz/functional-core-imperative-shell-scala
https://github.com/philipschwarz/functional-core-imperative-shell-haskell
Here I link up up some very useful material by Robert Norris (@tpolecat) and Martin Odersky (@odersky) to introduce Monad laws and reinforce the importance of checking the laws. E.g. while Option satisfies the laws, Try is not a lawful Monad: it trades the left identity law for the bullet-proof principle.
* ERRATA *
1) on the first slide the Kleisli composition signature appears three times, but one of the occurrences is incorrect in that it is (>=>)::(a->mb)->(b->mb)->(a->mc) whereas is should be (>=>)::(a->mb)->(b->mc)->(a->mc) - thank you Jules Ivanic.
From Scala Monadic Effects to Unison Algebraic EffectsPhilip Schwarz
Introduction to Unison’s algebraic effects (abilities) - go from a small Scala program based on the Option monad to a Unison program based on the Abort ability - inspired by, and part based on, a talk by Runar Bjarnason.
See here for the code:
https://github.com/philipschwarz/from-scala-monadic-effects-to-unison-algebraic-effects-scala-code
https://github.com/philipschwarz/from-scala-monadic-effects-to-unison-algebraic-effects-unison-code
N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit - Haskell and...Philip Schwarz
See how feeding FP workhorses map and filter with monadic steroids turns them into the intriguing mapM and filterM.
Graduate to foldM by learning how it behaves with the help of three simple yet instructive examples of its usage.
Use the powers of foldM to generate all permutations of a collection with a simple one-liner.
Exploit what you learned about foldM to solve the N-Queens Combinatorial Problem with an iterative approach rather than a recursive one.
Monad as functor with pair of natural transformationsPhilip Schwarz
Explains why a Monad is a functor with a pair of natural transformations (plus associativity and identity laws). It then explores this by looking at an example, with code in Scala.
Inspired and based on videos/publications by Bartosz Milewski, Rúnar Bjarnason and Rob Norris.
Download for better quality.
ScalaDays 2013 Keynote Speech by Martin OderskyTypesafe
Scala gives you awesome expressive power, but how to make best use of it? In my talk I will discuss the question what makes good Scala style. We will start with syntax and continue with how to name things, how to mix objects and functions, where (and where not) to use mutable state, and when to use which design pattern. As most questions of style, the discussion will be quite subjective, and some of it might be controversial. I am looking forward to discuss these topics with the conference attendees.
Objectives Assignment 09 Applications of Stacks COS.docxdunhamadell
Objectives
Assignment 09: Applications of Stacks
COSC 2336: Data Structures and Algorithms Fall 2020
• More practice with recursion.
• Practice writing some template functions.
• Use stack ADT to implement given algorithms.
• Practice using Stack class container given as a library in a separate file. • Look at some common applications of stacks.
Description
In this assignment, you will be using the Stack abstract data type we developed for this unit and discussed in our lectures, to implement 4 functions that use a stack data type to accomplish their algorithms. The functions range from relatively simple, straight forward use of a stack, to a bit more complex. But in all 4 cases, you should only need to use the abstract stack interface functions push(), pop(), top(), and isEmpty() in order to successfully use our Stack type for this assignment and the function you are asked to write.
NOTE
You are to use the Stack ADT abstraction give to you for this assignment. If you are familiar with STL stack containers, you are not to use them for this assignment. Part of the assignment is to look over and learn the Stack ADT implementation we give you here based on our textbook Stack examples.
Setup
For this assignment you will be given the following files:
File Name
assg09-tests.cpp assg09-stackfun.hpp assg09-stackfun.cpp Stack.hpp
Stack.cpp
Description
Unit tests for the member functions
you are to write.
Header file where function prototypes for the functions you write using stacks should go. Implementaiton file, the implementation of the 4 functions you write for this assignment go here. Header file defining a Stack ADT for use in implementing the functions for this assignment. You will not make any modifications in this file, you are only going to be using the given Stack. Implementation file for the Stack ADT
template class. You also do not make any changes in this file either.
Set up a multi-file project to compile the .cpp source files and run them as shown for the class. The Makefile you were given should be usable to create a build project using the Atom editor as required in this class. You will only be adding code to the assg09-stackfun.[hpp|cpp] file in this assignment. The Stack.[hpp|cpp] file contains a Stack container. You are to use this Stack ADT for the 4 functions you are to write for this assignment.
1
The general approach you should take for this assignment, and all assignment is:
Set up your project with the given starting code. The files should compile and run, but either no tests will be run, or tests will run but be failing.
For this project, start by uncommenting the first TEST_CASE in the assg09-tests.cpp file. These are the unit tests to test the functionality of your doParenthesisMatch() function, the member function you are to implement.
AddthecorrectfunctionprototypeforthedoParenthesisMatch()memberfunctionintheassg09-stackfun.hpp header file. The prototyp.
CMIS 102 Hands-On Lab
// Week 4
Overview:
This hands-on lab allows you to follow and experiment with the critical steps of developing a program including the program description, analysis, test plan, design (using both flow chart and pseudocode visualization), and implementation with C code. The example provided uses sequential, selection and repetition statements.
Program Description:
This program will calculate the sum of 10 integers. The program will ask the user to 10 integers. If the sum of the numbers is greater than 1000, a message is printed stating the sum is over 1000. The design step will include both pseudocode and flow chart visualization.
Analysis:
I will use sequential, selection and repetition programming statements.
I will define three integer numbers: count, value, sum. Count will store how many times values are entered to make sure we don’t exceed 10 values. Value will store the input integer and sum will store the running sum.
The sum will be calculated by this formula:
sum = sum + value
For example, if the first value entered was 4 and second was 10:
sum = sum + value = 0 + 4
sum = 4 + 10 = 14
Values and sum can be input and calculated within a repetition loop:
while count <10
Input value
sum = sum + value
End while
The additional selection statement will be of this form:
If sum > 1000 then
print "Sum is over 1000"
End If
Test Plan:
To verify this program is working properly the input values could be used for testing:
Test Case
Input
Expected Output
1
value=1
value=1
value=1
value=0
value=1
value=2
value=0
value=1
value=3
value=2
Sum = 12
2
value=100
value=100
value=100
value=100
value=100
value=200
value=200
value=200
value=200
value=200
Sum = 1200
Sum is over 1000.
3
value=-100
value=-100
value=-200
value=0
value=200
value=100
value=0
value=200
value=-300
value=-200
Sum = -400
Pseudocode:
// This program will calculate the sum of 10 integers.
// Declare variables
Declare count, value, sum as Integer
//Initialize Counter, Sum to 0
Set count=0
Set sum = 0
// Loop through 10 integers
While count < 10
Print “Enter an Integer”
Input value
sum = sum + value
count=count+1
End While
// Print results and messages
Print “Sum is “ + sum
If (sum > 1000)
Printf “Sum is over 1000”
End if
Flow Chart:
C Code
The following is the C Code that will compile in execute in the online compilers.
// C code
// This program will calculate the sum of 10 integers.
// Developer: Faculty CMIS102
// Date: Jan 31, 2014
#include <stdio.h>
int main ()
{
/* variable definition: */
int count, value, sum;
/* Initialize count and sum */
count = 0;
sum = 0;
// Loop through to input values
while (count < 10)
{
printf("Enter an Integer\n");
scanf("%d", &value);
sum = sum + value;
count = count + 1;
}
printf("Sum is %d\n " , sum );
if (sum >1000)
printf("Sum is over 1000\n");
return 0;
}
Setting up the code and the input parameters in ideone.com:
Note the input integer.
The Functional Programming Triad of fold, scan and iteratePhilip Schwarz
In the functional programming paradigm, the programmer does not need to write any loops or use array indices. Instead, functional programers deal with iterative calculations by translating mathematical induction directly into code, so that they can reason about sequences as mathematical values. The implement mathematical induction by folding, scanning and iterating.
This slide deck simply corrects a typo in the original (a missing 'h' in a couple of occurrences of 'mathematical')
Here is the first set of notes for the first class in Analysis of Algorithm. I added a dedicatory for my dear Fabi... she has showed me what real idealism is....
Principles of functional progrmming in scalaehsoon
a short outline on necessity of functional programming and principles of functional programming in Scala.
In the article some keyword are used but not explained (to keep the article short and simple), the interested reader can look them up in internet.
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidPhilip Schwarz
The subject of this deck is the small Print[A] program in the following blog post by Noel Welsh: https://www.inner-product.com/posts/direct-style-effects/.
Keywords: "direct-style", "context function", "context functions", "algebraic effect", "algebraic effects", "scala", "effect system", "effect systems", "effect", "side effect", "composition", "fp", "functional programming"
Folding Cheat Sheet #4 - fourth in a seriesPhilip Schwarz
For functions that can be defined both as an instance of a right fold and as an instance of a left fold, one may be more efficient than the other.
Let's look at the example of a function 'decimal' that converts a list of digits into the corresponding decimal number.
Erratum: it has been pointed out that it is possible to define the zip function using a right fold (see slide 5).
Tagless Final Encoding - Algebras and Interpreters and also ProgramsPhilip Schwarz
Tagless Final Encoding - Algebras and Interpreters and also Programs - An introduction, through the work of Gabriel Volpe.
Slide deck home: http://fpilluminated.com/assets/tagless-final-encoding-algebras-interpreters-and-programs.html
A sighting of traverseFilter and foldMap in Practical FP in ScalaPhilip Schwarz
Slide deck home: http://fpilluminated.com/assets/sighting-of-scala-cats-traverseFilter-and-foldMap-in-practical-fp-in-scala.html.
Download PDF for perfect image quality.
A sighting of sequence function in Practical FP in ScalaPhilip Schwarz
Slide deck home: http://fpilluminated.com/assets/sighting-of-scala-cats-sequence-function-in-practical-fp-in-scala.html.
Download PDF for perfect image quality.
This talk was presented on Aug 3rd 2023 during the Scala in the City event a ITV in London https://www.meetup.com/scala-in-the-city/events/292844968/
Visit the following for a description, slideshow, all slides with transcript, pdf, github repo, and eventually a video recording: http://fpilluminated.com/assets/n-queens-combinatorial-puzzle-meets-cats.html
At the centre of this talk is the N-Queens combinatorial puzzle. The reason why this puzzle features in the Scala book and functional programming course by Martin Odersky (the language’s creator), is that such puzzles are a particularly suitable application area of 'for comprehensions'.
We’ll start by (re)acquainting ourselves with the puzzle, and seeing the role played in it by permutations. Next, we’ll see how, when wanting to visualise candidate puzzle solutions, Cats’ monoidal functions fold and foldMap are a great fit for combining images.
While we are all very familiar with the triad providing the bread, butter and jam of functional programming, i.e. map, filter and fold, not everyone knows about the corresponding functions in Cats’ monadic variant of the triad, i.e. mapM, filterM and foldM, which we are going to learn about next.
As is often the case in functional programming, the traverse function makes an appearance, and we shall grab the opportunity to point out the symmetry that exists in the interrelation of flatMap / foldMap / traverse and flatten / fold / sequence.
Armed with an understanding of foldM, we then look at how such a function can be used to implement an iterative algorithm for the N-Queens puzzle.
The talk ends by pointing out that the iterative algorithm is smarter than the recursive one, because it ‘remembers’ where it has already placed previous queens.
Kleisli composition, flatMap, join, map, unit - implementation and interrelat...Philip Schwarz
Kleisli composition, flatMap, join, map, unit. A study/memory aid, to help learn/recall their implementation/interrelation.
Version 2, updated for Scala 3
Nat, List and Option Monoids -from scratch -Combining and Folding -an examplePhilip Schwarz
Nat, List and Option Monoids, from scratch. Combining and Folding: an example.
This is a new version of the original which has some cosmetic changes and a new 7th slide which only differs from slide 6 in that it defines the fold function in terms of the foldRight function.
Code: https://github.com/philipschwarz/nat-list-and-option-monoids-from-scratch-combining-and-folding-an-example
Nat, List and Option Monoids -from scratch -Combining and Folding -an examplePhilip Schwarz
Nat, List and Option Monoids, from scratch. Combining and Folding: an example.
Code: https://github.com/philipschwarz/nat-list-and-option-monoids-from-scratch-combining-and-folding-an-example
The Sieve of Eratosthenes - Part II - Genuine versus Unfaithful Sieve - Haske...Philip Schwarz
When I posted the deck for Part 1 to the Scala users forum, Odd Möller linked to a paper titled "The Genuine Sieve of Eratosthenes", which speaks of the Unfaithful Sieve.
Part 2 is based on that paper and on Richard Bird's faithful Haskell implementation of the Sieve, which we translate into Scala.
Scala code for Richard Bird's infinite primes Haskell program: https://github.com/philipschwarz/sieve-of-eratosthenes-part-2-scala
Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Ha...Philip Schwarz
Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Haskell, Scala and Java.
Inspired by the example in Scott Wlaschin’s F# book: Domain Modeling Made Functional.
Download for better results.
Java 19 Code: https://github.com/philipschwarz/fruit-salad-and-fruit-snack-ADT-example-java
Multiple Your Crypto Portfolio with the Innovative Features of Advanced Crypt...Hivelance Technology
Cryptocurrency trading bots are computer programs designed to automate buying, selling, and managing cryptocurrency transactions. These bots utilize advanced algorithms and machine learning techniques to analyze market data, identify trading opportunities, and execute trades on behalf of their users. By automating the decision-making process, crypto trading bots can react to market changes faster than human traders
Hivelance, a leading provider of cryptocurrency trading bot development services, stands out as the premier choice for crypto traders and developers. Hivelance boasts a team of seasoned cryptocurrency experts and software engineers who deeply understand the crypto market and the latest trends in automated trading, Hivelance leverages the latest technologies and tools in the industry, including advanced AI and machine learning algorithms, to create highly efficient and adaptable crypto trading bots
Experience our free, in-depth three-part Tendenci Platform Corporate Membership Management workshop series! In Session 1 on May 14th, 2024, we began with an Introduction and Setup, mastering the configuration of your Corporate Membership Module settings to establish membership types, applications, and more. Then, on May 16th, 2024, in Session 2, we focused on binding individual members to a Corporate Membership and Corporate Reps, teaching you how to add individual members and assign Corporate Representatives to manage dues, renewals, and associated members. Finally, on May 28th, 2024, in Session 3, we covered questions and concerns, addressing any queries or issues you may have.
For more Tendenci AMS events, check out www.tendenci.com/events
Listen to the keynote address and hear about the latest developments from Rachana Ananthakrishnan and Ian Foster who review the updates to the Globus Platform and Service, and the relevance of Globus to the scientific community as an automation platform to accelerate scientific discovery.
We describe the deployment and use of Globus Compute for remote computation. This content is aimed at researchers who wish to compute on remote resources using a unified programming interface, as well as system administrators who will deploy and operate Globus Compute services on their research computing infrastructure.
Enhancing Research Orchestration Capabilities at ORNL.pdfGlobus
Cross-facility research orchestration comes with ever-changing constraints regarding the availability and suitability of various compute and data resources. In short, a flexible data and processing fabric is needed to enable the dynamic redirection of data and compute tasks throughout the lifecycle of an experiment. In this talk, we illustrate how we easily leveraged Globus services to instrument the ACE research testbed at the Oak Ridge Leadership Computing Facility with flexible data and task orchestration capabilities.
Large Language Models and the End of ProgrammingMatt Welsh
Talk by Matt Welsh at Craft Conference 2024 on the impact that Large Language Models will have on the future of software development. In this talk, I discuss the ways in which LLMs will impact the software industry, from replacing human software developers with AI, to replacing conventional software with models that perform reasoning, computation, and problem-solving.
Advanced Flow Concepts Every Developer Should KnowPeter Caitens
Tim Combridge from Sensible Giraffe and Salesforce Ben presents some important tips that all developers should know when dealing with Flows in Salesforce.
Globus Compute wth IRI Workflows - GlobusWorld 2024Globus
As part of the DOE Integrated Research Infrastructure (IRI) program, NERSC at Lawrence Berkeley National Lab and ALCF at Argonne National Lab are working closely with General Atomics on accelerating the computing requirements of the DIII-D experiment. As part of the work the team is investigating ways to speedup the time to solution for many different parts of the DIII-D workflow including how they run jobs on HPC systems. One of these routes is looking at Globus Compute as a way to replace the current method for managing tasks and we describe a brief proof of concept showing how Globus Compute could help to schedule jobs and be a tool to connect compute at different facilities.
Software Engineering, Software Consulting, Tech Lead.
Spring Boot, Spring Cloud, Spring Core, Spring JDBC, Spring Security,
Spring Transaction, Spring MVC,
Log4j, REST/SOAP WEB-SERVICES.
A Comprehensive Look at Generative AI in Retail App Testing.pdfkalichargn70th171
Traditional software testing methods are being challenged in retail, where customer expectations and technological advancements continually shape the landscape. Enter generative AI—a transformative subset of artificial intelligence technologies poised to revolutionize software testing.
Climate Science Flows: Enabling Petabyte-Scale Climate Analysis with the Eart...Globus
The Earth System Grid Federation (ESGF) is a global network of data servers that archives and distributes the planet’s largest collection of Earth system model output for thousands of climate and environmental scientists worldwide. Many of these petabyte-scale data archives are located in proximity to large high-performance computing (HPC) or cloud computing resources, but the primary workflow for data users consists of transferring data, and applying computations on a different system. As a part of the ESGF 2.0 US project (funded by the United States Department of Energy Office of Science), we developed pre-defined data workflows, which can be run on-demand, capable of applying many data reduction and data analysis to the large ESGF data archives, transferring only the resultant analysis (ex. visualizations, smaller data files). In this talk, we will showcase a few of these workflows, highlighting how Globus Flows can be used for petabyte-scale climate analysis.
Unleash Unlimited Potential with One-Time Purchase
BoxLang is more than just a language; it's a community. By choosing a Visionary License, you're not just investing in your success, you're actively contributing to the ongoing development and support of BoxLang.
Code reviews are vital for ensuring good code quality. They serve as one of our last lines of defense against bugs and subpar code reaching production.
Yet, they often turn into annoying tasks riddled with frustration, hostility, unclear feedback and lack of standards. How can we improve this crucial process?
In this session we will cover:
- The Art of Effective Code Reviews
- Streamlining the Review Process
- Elevating Reviews with Automated Tools
By the end of this presentation, you'll have the knowledge on how to organize and improve your code review proces
Prosigns: Transforming Business with Tailored Technology SolutionsProsigns
Unlocking Business Potential: Tailored Technology Solutions by Prosigns
Discover how Prosigns, a leading technology solutions provider, partners with businesses to drive innovation and success. Our presentation showcases our comprehensive range of services, including custom software development, web and mobile app development, AI & ML solutions, blockchain integration, DevOps services, and Microsoft Dynamics 365 support.
Custom Software Development: Prosigns specializes in creating bespoke software solutions that cater to your unique business needs. Our team of experts works closely with you to understand your requirements and deliver tailor-made software that enhances efficiency and drives growth.
Web and Mobile App Development: From responsive websites to intuitive mobile applications, Prosigns develops cutting-edge solutions that engage users and deliver seamless experiences across devices.
AI & ML Solutions: Harnessing the power of Artificial Intelligence and Machine Learning, Prosigns provides smart solutions that automate processes, provide valuable insights, and drive informed decision-making.
Blockchain Integration: Prosigns offers comprehensive blockchain solutions, including development, integration, and consulting services, enabling businesses to leverage blockchain technology for enhanced security, transparency, and efficiency.
DevOps Services: Prosigns' DevOps services streamline development and operations processes, ensuring faster and more reliable software delivery through automation and continuous integration.
Microsoft Dynamics 365 Support: Prosigns provides comprehensive support and maintenance services for Microsoft Dynamics 365, ensuring your system is always up-to-date, secure, and running smoothly.
Learn how our collaborative approach and dedication to excellence help businesses achieve their goals and stay ahead in today's digital landscape. From concept to deployment, Prosigns is your trusted partner for transforming ideas into reality and unlocking the full potential of your business.
Join us on a journey of innovation and growth. Let's partner for success with Prosigns.
top nidhi software solution freedownloadvrstrong314
This presentation emphasizes the importance of data security and legal compliance for Nidhi companies in India. It highlights how online Nidhi software solutions, like Vector Nidhi Software, offer advanced features tailored to these needs. Key aspects include encryption, access controls, and audit trails to ensure data security. The software complies with regulatory guidelines from the MCA and RBI and adheres to Nidhi Rules, 2014. With customizable, user-friendly interfaces and real-time features, these Nidhi software solutions enhance efficiency, support growth, and provide exceptional member services. The presentation concludes with contact information for further inquiries.
Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala - Part 2
1. See aggregation functions defined inductively and implemented using recursion
Learn how in many cases, tail-recursion and the accumulator trick can be used to avoid stackoverflow errors
Watch as general aggregation is implemented and see duality theorems capturing the relationship between left folds and right folds
Part 2 - through the work of
Folding Unfolded
Polyglot FP for Fun and Profit
Haskell and Scala
@philip_schwarzslides by https://www.slideshare.net/pjschwarz
Sergei Winitzki
sergei-winitzki-11a6431
Richard Bird
http://www.cs.ox.ac.uk/people/richard.bird/
2. While Part 1 was centred on Richard Bird’s Introduction to Functional Programming using Haskell,
Part 2 is centred on Sergei Winitzki’s The Science of Functional Programming.
I hope Sergei will also forgive me for relying so heavily on his work, but I do not currently know of a
better, a more comprehensive, or a more thorough introduction to folding.
Sergei Winitzki
sergei-winitzki-11a6431
3. Sergei Winitzki
sergei-winitzki-11a6431
From the Preface:
This book is at once a reference text and a tutorial that teaches functional programmers how
to reason mathematically about types and code, in a manner directly relevant to software
practice.
…
The presentation is self-contained, defining and explaining all required ideas, notations, and
Scala language features from scratch. The aim is to make all mathematical notions and
derivations understandable.
…
The vision of this book is to explain the mathematical principles that guide the practice of
functional programming — i.e. principles that help us write code. So, all mathematical
developments in this book aremotivated and justified by practical programming issues and
are accompanied by Scala code that illustrates their usage.
…
Each concept or technique is motivated and explained to make it as simple as possible (“but
not simpler”) and also clarified via solved examples and exercises, which the readers will be
able to solve after reading the chapter.
…
A software engineer needs to know only a few fragments of mathematical theory; namely,
the fragments that answer questions arising in the practice of functional programming. So
this book keeps theoretical material at the minimum; ars longa, vita brevis.
…
Mathematical generalizations are not pursued beyond proven practical relevance or
immediate pedagogical usefulness.
https://github.com/winitzki/sofp
From the back cover:
This book is a pedagogically developed series of in-depth tutorials on functional programming.
The tutorials cover both the theory and the practice of functional programming, with the goal of building theoretical foundations that are valuable for practitioners.
Long and difficult, yet boring explanations are given in excruciating detail. Solved examples and step-by-step derivations are followed by exercises for self-study.
4. Sergei Winitzki
A software engineer needs to know
only a few fragments of mathematical
theory; namely, the fragments that
answer questions arising in the practice
of functional programming. So this
book keeps theoretical material at the
minimum; ars longa, vita brevis.
5. 2.2 Converting a sequence into a single value
Until this point, we have been working with sequences using methods such as .map and .zip. These techniques are powerful but still
insufficient for solving certain problems.
A simple computation that is impossible to do using .map is obtaining the sum of a sequence of numbers. The standard library method .sum
already does this; but we cannot re-implement .sum ourselves by using .map, .zip, or .filter. These operations always compute new
sequences, while we need to compute a single value (the sum of all elements) from a sequence.
We have seen a few library methods such as .count, .length, and .max that compute a single value from a sequence; but we still cannot
implement .sum using these methods. What we need is a more general way of converting a sequence to a single value, such that we could
ourselves implement .sum, .count, .max, and other similar computations.
Another task not solvable with .map, .sum, etc., is to compute a floating-point number from a given sequence of decimal digits (including
a “dot” character):
def digitsToDouble(ds: Seq[Char]): Double = ???
scala> digitsToDouble(Seq(’2’, ’0’, ’4’, ’.’, ’5’))
res0: Double = 204.5
Why is it impossible to implement this function using .map, .sum, and other methods we have seen so far? In fact, the same task for
integer numbers (instead of floating-point numbers) can be implemented via .length, .map, .sum, and .zip:
def digitsToInt(ds: Seq[Int]): Int = {
val n = ds.length
// Compute a sequence of powers of 10, e.g. [1000, 100, 10, 1].
val powers: Seq[Int] = (0 to n - 1).map(k => math.pow(10, n - 1 - k).toInt)
// Sum the powers of 10 with coefficients from ‘ds‘.
(ds zip powers).map { case (d, p) => d * p }.sum
}
scala> digitsToInt(Seq(2,4,0,5))
res0: Int = 2405
Sergei Winitzki
sergei-winitzki-11a6431
6. Yes, well spotted: we have already seen the
problem that is solved by digitsToInt in Part 1.
suppose we want a function 𝑑𝑒𝑐𝑖𝑚𝑎𝑙 that takes a list of digits and returns
the corresponding decimal number; thus
𝑑𝑒𝑐𝑖𝑚𝑎𝑙 [𝑥0, 𝑥1, … , 𝑥n] = ∑!"#
$
𝑥 𝑘10($&!)
It is assumed that the most significant digit comes first in the list.
7. This task is doable because the required computation can be written as the formula
𝑟 = 2
!"#
$&(
𝑑 𝑘 ∗ 10$&(&!
.
The sequence of powers of 10 can be computed separately and “zipped” with the sequence of digits 𝑑 𝑘 . However, for floating-
point numbers, the sequence of powers of 10 depends on the position of the “dot” character. Methods such as .map or .zip
cannot compute a sequence whose next elements depend on previous elements, and the dependence is described by some
custom function.
2.2.1 Inductive definitions of aggregation functions
Mathematical induction is a general way of expressing the dependence of next values on previously computed values. To define a
function from a sequence to a single value (e.g. an aggregation function f:Seq[Int] => Int) via mathematical induction, we
need to specify two computations:
• (The base case of the induction.) We need to specify what value the function f returns for an empty sequence, Seq(). The
standard method isEmpty can be used to detect empty sequences. In case the function f is only defined for non-empty
sequences, we need to specify what the function f returns for a one-element sequence such as Seq(x), with any x.
• (The inductive step.) Assuming that the function f is already computed for some sequence xs (the inductive assumption), how
to compute the function f for a sequence with one more element x? The sequence with one more element is written as xs
:+ x. So, we need to specify how to compute f(xs :+ x) assuming that f(xs) is already known.
Once these two computations are specified, the function f is defined (and can in principle be computed) for an arbitrary input
sequence. This is how induction works in mathematics, and it works in the same way in functional programming. With this
approach, the inductive definition of the method .sum looks like this:
• The sum of an empty sequence is 0. That is, Seq().sum == 0.
• If the result is already known for a sequence xs, and we have a sequence that has one more element x, the new result is equal
to xs.sum + x. In code, this is (xs :+ x).sum == xs.sum + x.
Sergei Winitzki
sergei-winitzki-11a6431
8. The inductive definition of the function digitsToInt is:
• For an empty sequence of digits, Seq(), the result is 0. This is a convenient base case, even if we never call digitsToInt on
an empty sequence.
• If digitsToInt(xs) is already known for a sequence xs of digits, and we have a sequence xs :+ x with one more digit x,
then
digitsToInt(xs :+ x) = digitsToInt(xs) * 10 + x
Let us write inductive definitions for methods such as .length, .max, and .count:
• The length of a sequence:
– for an empty sequence, Seq().length == 0
– if xs.length is known then (xs :+ x).length == xs.length + 1
• Maximum element of a sequence (undefined for empty sequences):
– for a one-element sequence, Seq(x).max == x
– if xs.max is known then (xs :+ x).max == math.max(xs.max, x)
• Count the sequence elements satisfying a predicate p:
– for an empty sequence, Seq().count(p) == 0
– if xs.count(p) is known then (xs :+ x).count(p) == xs.count(p) + c, where we set c = 1
when p(x) == true and c = 0 otherwise
There are two main ways of translating mathematical induction into code. The first way is to write a recursive function. The
second way is to use a standard library function, such as foldLeft or reduce.
Most often it is better to use the standard library functions, but sometimes the code is more transparent when using explicit
recursion. So let us consider each of these ways in turn. Sergei Winitzki
sergei-winitzki-11a6431
9. 2.2.2 Implementing functions by recursion
A recursive function is any function that calls itself somewhere within its own body. The call to itself is the recursive call.
When the body of a recursive function is evaluated, it may repeatedly call itself with different arguments until the result value can
be computed without any recursive calls. The last recursive call corresponds to the base case of the induction. It is an error if the
base case is never reached, as in this example:
scala> def infiniteLoop(x: Int): Int = infiniteLoop(x+1)
infiniteLoop : (x: Int)Int
scala> infiniteLoop(2) // You will need to press Ctrl-C to stop this.
We translate mathematical induction into code by first writing a condition to decide whether we have the base case or the
inductive step. As an example, let us define .sum by recursion. The base case returns 0, and the inductive step returns a value
computed from the recursive call:
def sum(s: Seq[Int]): Int = if (s.isEmpty) 0 else {
val x = s.head // To split s = x +: xs, compute x
val xs = s.tail // and xs.
sum(xs) + x // Call sum(...) recursively.
}
In this example, the if/else expression will separate the base case from the inductive step. In the inductive step, it is
convenient to split the given sequence s into its first element x, or the head of s, and the remainder tail sequence xs. So, we
split s as s = x +: xs rather than as s = xs :+ x (footnote: It is easier to remember the meaning of x +: xs and xs :+
x if we note that the colon always points to the collection).
For computing the sum of a numerical sequence, the order of summation does not matter. However, the order of operations will
matter for many other computational tasks. We need to choose whether the inductive step should split the sequence as s = x
+: xs or as s = xs :+ x, according to the task at hand.
Sergei Winitzki
sergei-winitzki-11a6431
10. Consider the implementation of digitsToInt according to the inductive definition shown in the previous subsection:
def digitsToInt(s: Seq[Int]): Int = if (s.isEmpty) 0 else {
val x = s.last // To split s = xs :+ x, compute x
val xs = s.take(s.length - 1) // and xs.
digitsToInt(xs) * 10 + x // Call digitstoInt(...) recursively.
}
In this example, it is important to split the sequence into s = xs :+ x in this order, and not in the order x +: xs. The
reason is that digits increase their numerical value from right to left, so we need to multiply the value of the left subsequence,
digitsToInt(xs), by 10, in order to compute the correct result.
These examples show how mathematical induction is converted into recursive code. This approach often works but has two
technical problems. The first problem is that the code will fail due to a “stack overflow” when the input sequence s is long
enough. In the next subsection, we will see how this problem is solved (at least in some cases) using “tail recursion”.
The second problem is that each inductively defined function repeats the code for checking the base case and the code for
splitting the sequence s into the subsequence xs and the extra element x. This repeated common code can be put into a library
function, and the Scala library provides such functions. We will look at using them in Section 2.2.4.
The inductive definition of the function digitsToInt is:
• For an empty sequence of digits, Seq(), the result is 0. This is a convenient base case, even if we never call digitsToInt
on an empty sequence.
• If digitsToInt(xs) is already known for a sequence xs of digits, and we have a sequence xs :+ x with one more digit
x, then
digitsToInt(xs :+ x) = digitsToInt(xs) * 10 + x
Sergei Winitzki
sergei-winitzki-11a6431
11. def digitsToInt(s: Seq[Int]): Int = if (s.isEmpty) 0 else {
val x = s.last // To split s = xs :+ x, compute x
val xs = s.take(s.length - 1) // and xs.
digitsToInt(xs) * 10 + x // Call digitstoInt(...) recursively.
}
def sum(s: Seq[Int]): Int = if (s.isEmpty) 0 else {
val x = s.head // To split s = x +: xs, compute x
val xs = s.tail // and xs.
sum(xs) + x // Call sum(...) recursively.
}
For computing the sum of a numerical sequence, the order of summation
does not matter. However, the order of operations will matter for many
other computational tasks.
We need to choose whether the inductive step should split the sequence as
s = x +: xs
or as
s = xs :+ x,
according to the task at hand.
This slide, which repeats the definitions of sum and digitsToInt, is just here
to reinforce the idea that in many tasks, the order of operations matters.
Sergei Winitzki
12. def digitsToInt(s: Seq[Int]): Int = if (s.isEmpty) 0 else {
val x = s.last // To split s = xs :+ x, compute x
val xs = s.take(s.length - 1) // and xs.
digitsToInt(xs) * 10 + x // Call digitstoInt(...) recursively.
}
To illustrate, suppose we want a function decimal that takes a list of digits and returns the corresponding
decimal number; thus
𝑑𝑒𝑐𝑖𝑚𝑎𝑙 [𝑥0, 𝑥1, … , 𝑥n] = ∑!"#
$
𝑥 𝑘10($&!)
It is assumed that the most significant digit comes first in the list. One way to compute decimal efficiently is by
a process of multiplying each digit by ten and adding in the following digit. For example
𝑑𝑒𝑐𝑖𝑚𝑎𝑙 𝑥0, 𝑥1, 𝑥2 = 10 × 10 × 10 × 0 + 𝑥0 + 𝑥1 + 𝑥2
This decomposition of a sum of powers is known as Horner’s rule.
Yes, this solution is an
implementation of the rule
we saw in Part 1
13. 2.2.3 Tail recursion
The code of lengthS will fail for large enough sequences. To see why, consider an inductive definition of the .length method as a
function lengthS:
def lengthS(s: Seq[Int]): Int =
if (s.isEmpty) 0
else 1 + lengthS(s.tail)
scala> lengthS((1 to 1000).toList)
res0: Int = 1000
scala> val s = (1 to 100_000).toList
s : List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31, 32, ...
scala> lengthS(s)
java.lang.StackOverflowError
at .lengthS(<console>:12)
at .lengthS(<console>:12)
at .lengthS(<console>:12)
at .lengthS(<console>:12)
...
The problem is not due to insufficient main memory: we are able to compute and hold in memory the entire sequence s. The
problem is with the code of the function lengthS. This function calls itself inside the expression 1 + lengthS(...). So we can
visualize how the computer evaluates this code:
lengthS(Seq(1, 2, ..., 100000))
= 1 + lengthS(Seq(2, ..., 100000))
= 1 + (1 + lengthS(Seq(3, ..., 100000)))
= ...
Sergei Winitzki
sergei-winitzki-11a6431
14. The function body of lengthS will evaluate the inductive step, that is, the “else” part of the “if/else”, about 100_000 times. Each
time, the sub-expression with nested computations 1+(1+(...)) will get larger.
This intermediate sub-expression needs to be held somewhere in memory, until at some point the function body goes into the
base case and returns a value. When that happens, the entire intermediate sub-expression will contain about 100_000_nested
function calls still waiting to be evaluated.
This sub-expression is held in a special area of memory called stack memory, where the not-yet-evaluated nested function calls
are held in the order of their calls, as if on a “stack”. Due to the way computer memory is managed, the stack memory has a fixed
size and cannot grow automatically. So, when the intermediate expression becomes large enough, it causes an overflow of the
stack memory and crashes the program.
A way to avoid stack overflows is to use a trick called tail recursion. Using tail recursion means rewriting the code so that all
recursive calls occur at the end positions (at the “tails”) of the function body. In other words, each recursive call must be itself the
last computation in the function body, rather than placed inside other computations. Here is an example of tail-recursive code:
def lengthT(s: Seq[Int], res: Int): Int =
if (s.isEmpty)
res
else
lengthT(s.tail, 1 + res)
In this code, one of the branches of the if/else returns a fixed value without doing any recursive calls, while the other branch
returns the result of a recursive call to lengthT(...). In the code of lengthT, recursive calls never occur within any sub-
expressions.
def lengthS(s: Seq[Int]): Int =
if (s.isEmpty) 0
else 1 + lengthS(s.tail)
lengthS(Seq(1, 2, ..., 100000))
= 1 + lengthS(Seq(2, ..., 100000))
= 1 + (1 + lengthS(Seq(3, ..., 100000)))
= ...
Sergei Winitzki
sergei-winitzki-11a6431
15. It is not a problem that the recursive call to lengthT has some sub-expressions such as 1 + res as its arguments, because all these
sub-expressions will be computed before lengthT is recursively called.
The recursive call to lengthT is the last computation performed by this branch of the if/else. A tail-recursive function can have
many if/else or match/case branches, with or without recursive calls; but all recursive calls must be always the last expressions
returned.
The Scala compiler has a feature for checking automatically that a function’s code is tail-recursive : the @tailrec annotation. If a
function with a @tailrec annotation is not tail-recursive, or is not recursive at all, the program will not compile.
@tailrec def lengthT(s: Seq[Int], res: Int): Int =
if (s.isEmpty) res
else lengthT(s.tail, 1 + res)
Let us trace the evaluation of this function on an example:
lengthT(Seq(1,2,3), 0)
= lengthT(Seq(2,3), 1 + 0) // = lengthT(Seq(2,3), 1)
= lengthT(Seq(3), 1 + 1) // = lengthT(Seq(3), 2)
= lengthT(Seq(), 1 + 2) // = lengthT(Seq(), 3)
= 3
All sub-expressions such as 1 + 1 and 1 + 2 are computed before recursive calls to lengthT. Because of that, sub-expressions
do not grow within the stack memory. This is the main benefit of tail recursion.
How did we rewrite the code of lengthS to obtain the tail-recursive code of lengthT? An important difference between lengthS
and lengthT is the additional argument, res, called the accumulator argument. This argument is equal to an intermediate result of
the computation. The next intermediate result (1 + res) is computed and passed on to the next recursive call via the
accumulator argument. In the base case of the recursion, the function now returns the accumulated result, res, rather than 0,
because at that time the computation is finished. Rewriting code by adding an accumulator argument to achieve tail recursion is
called the accumulator technique or the “accumulator trick”.
def lengthS(s: Seq[Int]): Int =
if (s.isEmpty) 0
else 1 + lengthS(s.tail)
Sergei Winitzki
sergei-winitzki-11a6431
16. One consequence of using the accumulator trick is that the function lengthT now always needs a value for the accumulator
argument. However, our goal is to implement a function such as length(s) with just one argument, s:Seq[Int]. We can define
length(s) = lengthT(s, ???) if we supply an initial accumulator value. The correct initial value for the accumulator is 0, since
in the base case (an empty sequence s) we need to return 0.
So, a tail-recursive implementation of lengthT requires us to define two functions: the tail-recursive lengthT and an “adapter”
function that will set the initial value of the accumulator argument. To emphasize that lengthT is a helper function, one could
define it inside the adapter function:
def length[A](s: Seq[A]): Int = {
@tailrec def lengthT(s: Seq[A], res: Int): Int = {
if (s.isEmpty) res
else lengthT(s.tail, 1 + res)
}
lengthT(s, 0)
}
When length is implemented like that, users will not be able to call lengthT directly, because it is only visible within the body of
the length function.
Another possibility in Scala is to use a default value for the res argument:
@tailrec def length(s: Seq[A], res: Int = 0): Int =
if (s.isEmpty) res
else length(s.tail, 1 + res)
Giving a default value for a function argument is the same as defining two functions: one with that argument and one without. For
example, the syntax
def f(x: Int, y: Boolean = false): Int = ... // Function body.
Sergei Winitzki
sergei-winitzki-11a6431
17. is equivalent to defining two functions (with the same name),
def f(x: Int, y: Boolean) = ... // Function body.
def f(x: Int): Int = f(Int, false)
Using a default argument value, we can define the tail-recursive helper function and the adapter function at once, making the
code shorter.
The accumulator trick works in a large number of cases, but it may be far from obvious how to introduce the accumulator
argument, what its initial value must be, and how to define the inductive step for the accumulator. In the example with the
lengthT function, the accumulator trick works because of the following mathematical property of the expression being computed:
1 + (1 + (1 + (... + 1))) = (((1 + 1) + 1) + ...) + 1 .
This is the associativity law of addition. Due to that law, the computation can be rearranged so that additions associate to the
left. In code, it means that intermediate expressions are computed immediately before making recursive calls; this avoids the
growth of the intermediate expressions.
Usually, the accumulator trick works because some associativity law is present. In that case, we are able to rearrange the order of
recursive calls so that these calls always occur outside all other subexpressions, — that is, in tail positions. However, not all
computations obey a suitable associativity law. Even if a code rearrangement exists, it may not be immediately obvious how to find
it.
Sergei Winitzki
sergei-winitzki-11a6431
18. def digitsToInt(s: Seq[Int]): Int = if (s.isEmpty) 0 else {
val x = s.last // To split s = xs :+ x, compute x
val xs = s.take(s.length - 1) // and xs.
digitsToInt(xs) * 10 + x // Call digitstoInt(...) recursively.
}
As an example, consider a tail-recursive re-implementation of the function digitsToInt from the previous subsection where the
recursive call is within a sub-expression digitsToInt(xs) * 10 + x. To transform the code into a tail-recursive form, we need to
rearrange the main computation,
r = dn−1 + 10 ∗ (dn−2 +10 ∗ (dn−3 + 10 ∗ (...d0)))
so that the operations group to the left. We can do this by rewriting r as
r = ((d0 ∗ 10 + d1) ∗ 10 + ...) ∗ 10 + dn−1
It follows that the digit sequence s must be split into the leftmost digit and the rest, s = s.head +: s.tail. So, a tail-recursive
implementation of the above formula is
@tailrec def fromDigits(s: Seq[Int], res: Int = 0): Int =
// ‘res‘ is the accumulator.
if (s.isEmtpy) res
else fromDigits(s.tail, 10 * res + s.head)
Despite a certain similarity between this code and the code of digitsToInt from the previous subsection, the implementation
fromDigits cannot be directly derived from the inductive definition of digitsToInt. One needs a separate proof that
fromDigits(s, 0) computes the same result as digitsToInt(s). The proof follows from the following property.
Statement 2.2.3.1 For any xs: Seq[Int] and r: Int, we have
fromDigits(xs, r) = digitsToInt(xs) + r * math.pow(10, s.length)
Proof We prove this by induction. <…proof omitted…>
Sergei Winitzki
sergei-winitzki-11a6431
not tail-recursive
19. To illustrate, suppose we want a function decimal that takes a list of digits and returns the
corresponding decimal number; thus
𝑑𝑒𝑐𝑖𝑚𝑎𝑙 [𝑥0, 𝑥1, … , 𝑥n] = ∑!"#
$
𝑥 𝑘10($&!)
It is assumed that the most significant digit comes first in the list. One way to compute decimal
efficiently is by a process of multiplying each digit by ten and adding in the following digit. For
example
𝑑𝑒𝑐𝑖𝑚𝑎𝑙 𝑥0, 𝑥1, 𝑥2 = 10 × 10 × 10 × 0 + 𝑥0 + 𝑥1 + 𝑥2
This decomposition of a sum of powers is known as Horner’s rule.
Suppose we define ⊕ by 𝑛 ⊕ 𝑥 = 10 × 𝑛 + 𝑥. Then we can rephrase the above equation as
𝑑𝑒𝑐𝑖𝑚𝑎𝑙 𝑥0, 𝑥1, 𝑥2 = (0 ⊕ 𝑥0) ⊕ 𝑥1 ⊕ 𝑥2
@tailrec def fromDigits(s: Seq[Int], res: Int = 0): Int =
// ‘res‘ is the accumulator.
if (s.isEmpty) res
else fromDigits(s.tail, 10 * res + s.head)
Yes, this solution uses the ⊕
function and the ‘rephrased’
equation we saw in Part 1
20. 2.2.4 Implementing general aggregation (foldLeft)
An aggregation converts a sequence of values into a single value. In general, the type of the result may be
different from the type of sequence elements. To describe that general situation, we introduce type parameters, A
and B, so that the input sequence is of type Seq[A] and the aggregated value is of type B. Then an inductive
definition of any aggregation function f: Seq[A] => B looks like this:
• (Base case.) For an empty sequence, f(Seq()) = b0 where b0:B is a given value.
• (Inductive step.) Assuming that f(xs) = b is already computed, we define f(xs :+ x) = g(x, b) where g is a
given function with type signature g:(A, B) => B.
The code implementing f is written using recursion:
def f[A, B](s: Seq[A]): B =
if (s.isEmpty) b0
else g(s.last, f(s.take(s.length - 1)))
We can now refactor this code into a generic utility function, by making b0 and g into parameters. A possible
implementation is
def f[A, B](s: Seq[A], b: B, g: (A, B) => B): B =
if (s.isEmpty) b
else g(s.last, f(s.take(s.length - 1), b, g)
However, this implementation is not tail-recursive.
Sergei Winitzki
sergei-winitzki-11a6431
21. Applying f to a sequence of, say, three elements, Seq(x, y, z), will create an intermediate expression g(z, g(y, g(x, b))).
This expression will grow with the length of s, which is not acceptable.
To rearrange the computation into a tail-recursive form, we need to start the base case at the innermost call g(x, b), then
compute g(y, g(x, b)) and continue. In other words, we need to traverse the sequence starting from its leftmost element x,
rather than starting from the right. So, instead of splitting the sequence s into s.take(s.length - 1) :+ s.last as we did in
the code of f, we need to split s into s.head :+ s.tail. Let us also exchange the order of the arguments of g, in order to be
more consistent with the way this code is implemented in the Scala library. The resulting code is tail-recursive:
@tailrec def leftFold[A, B](s: Seq[A], b: B, g: (B, A) => B): B =
if (s.isEmpty) b
else leftFold(s.tail, g(b, s.head), g)
We call this function a “left fold” because it aggregates (or “folds”) the sequence starting from the leftmost element.
In this way, we have defined a general method of computing any inductively defined aggregation function on a sequence. The
function leftFold implements the logic of aggregation defined via mathematical induction. Using leftFold, we can write
concise implementations of methods such as .sum, .max, and many other aggregation functions. The method leftFold already
contains all the code necessary to set up the base case and the inductive step. The programmer just needs to specify the
expressions for the initial value b and for the updater function g.
def f[A, B](s: Seq[A], b: B, g: (A, B) => B): B =
if (s.isEmpty) b
else g(s.last, f(s.take(s.length - 1), b, g)
Sergei Winitzki
sergei-winitzki-11a6431
22. I think it is worth repeating some of what we just
saw on the previous slide, so it sinks in better
Sergei Winitzki
@tailrec def leftFold[A, B](s: Seq[A], b: B, g: (B, A) => B): B =
if (s.isEmpty) b
else leftFold(s.tail, g(b, s.head), g)
We call this function a “left fold” because it aggregates (or “folds”) the sequence starting from
the leftmost element.
In this way, we have defined a general method of computing any inductively defined
aggregation function on a sequence.
The function leftFold implements the logic of aggregation defined via mathematical induction.
Using leftFold, we can write concise implementations of methods such as .sum, .max, and many
other aggregation functions.
The method leftFold already contains all the code necessary to set up the base case and the
inductive step. The programmer just needs to specify the expressions for the initial value b and
for the updater function g.
23. As a first example, let us use leftFold for implementing the .sum method:
def sum(s: Seq[Int]): Int = leftFold(s, 0, { (x, y) => x + y })
To understand in detail how leftFold works, let us trace the evaluation of this function when applied to Seq(1, 2, 3):
// Here, g = { (x, y) => x + y }, so g(x, y) = x + y.
== leftFold(Seq(2, 3), g(0, 1), g) // g (0, 1) = 1.
== leftFold(Seq(2, 3), 1, g) // Now expand the code of ‘leftFold‘.
== leftFold(Seq(3), g(1, 2), g) // g(1, 2) = 3; expand the code.
== leftFold(Seq(), g(3, 3), g) // g(3, 3) = 6; expand the code.
== 6
The second argument of leftFold is the accumulator argument. The initial value of the accumulator is specified when first calling
leftFold. At each iteration, the new accumulator value is computed by calling the updater function g, which uses the previous
accumulator value and the value of the next sequence element. To visualize the process of recursive evaluation, it is convenient to
write a table showing the sequence elements and the accumulator values as they are updated:
We implemented leftFold only as an illustration. Scala’s library has a method called .foldLeft implementing the same logic
using a slightly different type signature. To see this difference, compare the implementation of sum using our leftFold function
and using the standard .foldLeft method:
def sum(s: Seq[Int]): Int = leftFold(s, 0, { (x, y) => x + y })
def sum(s: Seq[Int]): Int = s.foldLeft(0) { (x, y) => x + y }
Current element x Old accumulator value New accumulator value
1 0 1
2 1 3
3 3 6
Sergei Winitzki
sergei-winitzki-11a6431
24. The syntax of .foldLeft makes it more convenient to use a nameless function as the updater argument of .foldLeft, since curly
braces separate that argument from others. We will use the standard .foldLeft method from now on.
In general, the type of the accumulator value can be different from the type of the sequence elements. An example is an
implementation of count:
def count[A](s: Seq[A], p: A => Boolean): Int =
s.foldLeft(0) { (x, y) => x + (if (p(y)) 1 else 0) }
The accumulator is of type Int, while the sequence elements can have an arbitrary type, parameterized by A. The .foldLeft
method works in the same way for all types of accumulators and all types of sequence elements.
The method .foldLeft is available in the Scala library for all collections, including dictionaries and sets. Since .foldLeft is tail-
recursive, no stack overflows will occur even for very large sequences.
The Scala library contains several other methods similar to .foldLeft, such as .foldRight and .reduce. (However, .foldRight
is not tail-recursive!)
def sum(s: Seq[Int]): Int = leftFold(s, 0, { (x, y) => x + y })
def sum(s: Seq[Int]): Int = s.foldLeft(0) { (x, y) => x + y }
Sergei Winitzki
sergei-winitzki-11a6431
25. In Introduction to Functional Programming using Haskell, there is a section
covering the laws of fold, which include three duality theorems.
4.6 Laws of fold
There are a number of important laws concerning 𝑓𝑜𝑙𝑑𝑟 and its relationship with 𝑓𝑜𝑙𝑑𝑙. As we saw in section 3.3, instead of
having to prove a property of a recursive function over a recursive datatype by writing down an explicit induction proof, one can
often phrase the property as an instance of one of the laws of the 𝑓𝑜𝑙𝑑 operator for the datatype.
4.6.1 Duality theorems
The first three laws are called duality theorems and concern the relationship between 𝑓𝑜𝑙𝑑𝑟 and 𝑓𝑜𝑙𝑑𝑙.
What we are going to do in the next seven slides is look back at three of the
functions that Sergei Winitzki discussed in his book, and relate them to the three
duality theorems.
@philip_schwarz
26. We translate mathematical induction into code by first writing a condition to decide whether we have the base case or the
inductive step. As an example, let us define .sum by recursion. The base case returns 0, and the inductive step returns a value
computed from the recursive call:
def sum(s: Seq[Int]): Int = if (s.isEmpty) 0 else {
val x = s.head // To split s = x +: xs, compute x
val xs = s.tail // and xs.
sum(xs) + x // Call sum(...) recursively.
}
In this example, the if/else expression will separate the base case from the inductive step. In the inductive step, it is
convenient to split the given sequence s into its first element x, or the head of s, and the remainder tail sequence xs. So, we
split s as s = x +: xs rather than as s = xs :+ x
For computing the sum of a numerical sequence, the order of summation does not matter.
Remember earlier when Sergei Winitzki explained that for computing the
sum of a numerical sequence, the order of summation does not matter?
If the order of summation doesn’t matter, does that mean that it is possible
to implement the sum function both using a right fold and using a left fold?
The answer is yes, but with the qualification mentioned on the nexts slide.
Sergei Winitzki
27. def foldr[A,B](f: (A,B) => B)(e: B)(s: List[A]): B = s match {
case Nil => e
case x::xs => f(x,foldr(f)(e)(xs))
}
def foldl[A,B](f: (B,A) => B)(e: B) (s: List[A]): B = s match {
case Nil => e
case x::xs => foldl(f)(f(e,x))(xs)
}
def add(x: Int, y: Int): Int = x + y
def sumr(s: List[Int]): Int = foldr(add)(0)(s)
def suml(s: List[Int]): Int = foldl(add)(0)(s)
assert( sumr(List(1,2,3,4,5)) == 15)
assert( suml(List(1,2,3,4,5)) == 15)
That works: we get the same result.
But if we pass foldr a sufficiently
large sequence, it encounters a
stack overflow error, since foldr is
not tail-recursive.
val oneTo40K = List.range(1,40_000)
assert( suml(oneTo40K) == 799_980_000)
assert(
try {
sumr(oneTo40K)
false
} catch {
case _:StackOverflowError => true
}
)
First, let’s define foldr and foldl.
Yes we are using List[A] rather
than Seq[A], simply to be
consistent with the foldr and foldl
definitions seen in in Part 1 (we’ll be
doing so throughout the slides on
the duality theorems).
Next, let’s define sumr using
foldr and suml using foldl.
@philip_schwarz
We had already seen the
Scala version of foldr in
Part1, but not of foldl.
28. First duality theorem. Suppose ⊕ is associative with unit 𝑒. Then
𝑓𝑜𝑙𝑑𝑟 ⊕ 𝑒 𝑥𝑠 = 𝑓𝑜𝑙𝑑𝑙 ⊕ 𝑒 𝑥𝑠
For all finite lists 𝑥𝑠.
For example, we could have defined
𝑠𝑢𝑚 = 𝑓𝑜𝑙𝑑𝑙 + 0
and = 𝑓𝑜𝑙𝑑𝑙 (⋀) 𝑇𝑟𝑢𝑒
concat = 𝑓𝑜𝑙𝑑𝑙 (⧺) [ ]
However, as we will elaborate in chapter 7, it is sometimes more efficient to implement
a function using 𝑓𝑜𝑙𝑑𝑙, and sometimes more efficient to use 𝑓𝑜𝑙𝑑𝑟.
The reason why foldr(add)(0)(s) produces the same result as foldl(add)(0)(s) (except when
foldr overflows the stack, of course), is that addition, 0 and s satisfy the constraints of the first duality
theorem, in that addition is an associative operation, 0 is the unit of addition, and s is a finite sequence.
e.g. see the slide after next for how the efficiency of 𝑟𝑒𝑣𝑒𝑟𝑠𝑒
is affected by whether it is implemented using 𝑓𝑜𝑙𝑑𝑟 or 𝑓𝑜𝑙𝑑𝑙.
𝑓𝑜𝑙𝑑𝑙 ∷ 𝛽 → 𝛼 → 𝛽 → 𝛽 → 𝛼 → 𝛽
𝑓𝑜𝑙𝑑𝑙 𝑓 𝑒 = 𝑒
𝑓𝑜𝑙𝑑𝑙 𝑓 𝑒 𝑥 ∶ 𝑥𝑠 = 𝑓𝑜𝑙𝑑𝑙 𝑓 𝑓 𝑒 𝑥 𝑥𝑠
𝑓𝑜𝑙𝑑𝑟 ∷ 𝛼 → 𝛽 → 𝛽 → 𝛽 → 𝛼 → 𝛽
𝑓𝑜𝑙𝑑𝑟 𝑓 𝑒 = 𝑒
𝑓𝑜𝑙𝑑𝑟 𝑓 𝑒 𝑥 ∶ 𝑥𝑠 = 𝑓 𝑥 𝑓𝑜𝑙𝑑𝑟 𝑓 𝑒 𝑥𝑠
def foldr[A,B](f: (A,B) => B)(e: B)(s: List[A]): B =
s match {
case Nil => e
case x::xs => f(x,foldr(f)(e)(xs)) }
def foldl[A,B](f: (B,A) => B)(e: B) (s: List[A]): B =
s match {
case Nil => e
case x::xs => foldl(f)(f(e,x))(xs) }
29. def lengthS(s: Seq[Int]): Int =
if (s.isEmpty) 0
else 1 + lengthS(s.tail)
@tailrec def length(s: Seq[A], res: Int = 0): Int =
if (s.isEmpty) res
else length(s.tail, 1 + res)
Remember earlier when Sergei Winitzki first
implemented a lengthS function that was not
tail-recursive and then implemented a length
function that was tail recursive?
Let’s implement the first function using foldr
and the second function using foldl.
def lengthr[A](s: List[A]): Int = {
def onePlus(a: A, n: Int): Int = 1 + n
foldr(onePlus)(0)(s)
}
def lengthl[A](s: List[A]): Int = {
def plusOne(n: Int, a: A): Int = 1 + n
foldl(plusOne)(0)(s)
}
That works: we get the same result.
assert( lengthr(List(1,2,3,4,5)) == 5)
assert( lengthl(List(1,2,3,4,5)) == 5)
30. The reason why foldr(onePlus)(0)(s) produces the same result as foldl(plusOne)(0)(s) (except when
foldr overflows the stack, of course), is that onePlus, plusOne, 0, and s satisfy the constraints of the second
duality theorem.
Second duality theorem. This is a generalization of the first. Suppose ⊕, ⊗, and 𝑒 are such that for all 𝑥, 𝑦, and 𝑧 we have
𝑥 ⊕ 𝑦 ⊗ 𝑧 = 𝑥 ⊕ 𝑦 ⊗ 𝑧
𝑥 ⊕ 𝑒 = 𝑒 ⊗ 𝑥
In other words, ⊕ and ⊗ associate with each other, and 𝑒 on the right of ⊕ is equivalent to 𝑒 on the left of ⊗. Then
𝑓𝑜𝑙𝑑𝑟 ⊕ 𝑒 𝑥𝑠 = 𝑓𝑜𝑙𝑑𝑙 ⊗ 𝑒 𝑥𝑠
For all finite lists 𝑥𝑠.
…
The second duality theorem has the first duality theorem as a special case, namely when
⊕ = ⊗
To illustrate the second duality theorem, consider the following definitions
𝑙𝑒𝑛𝑔𝑡ℎ ∷ [α] → 𝑰𝒏𝒕
𝑙𝑒𝑛𝑔𝑡ℎ = 𝑓𝑜𝑙𝑑𝑟 𝑜𝑛𝑒𝑝𝑙𝑢𝑠 0, 𝒘𝒉𝒆𝒓𝒆 𝑜𝑛𝑒𝑝𝑙𝑢𝑠 𝑥 𝑛 = 1 + 𝑛
𝑙𝑒𝑛𝑔𝑡ℎ = 𝑓𝑜𝑙𝑑𝑙 𝑝𝑙𝑢𝑠𝑜𝑛𝑒 0, 𝒘𝒉𝒆𝒓𝒆 𝑝𝑙𝑢𝑠𝑜𝑛𝑒 𝑛 𝑥 = 𝑛 + 1
reverse ∷ α → [α]
reverse = 𝑓𝑜𝑙𝑑𝑟 𝑠𝑛𝑜𝑐 , 𝒘𝒉𝒆𝒓𝒆 𝑠𝑛𝑜𝑐 𝑥 𝑥𝑠 = 𝑥𝑠 ⧺ [𝑥]
reverse = 𝑓𝑜𝑙𝑑𝑙 𝑐𝑜𝑛𝑠 , 𝒘𝒉𝒆𝒓𝒆 𝑐𝑜𝑛𝑠 𝑥𝑠 𝑥 = 𝑥 : 𝑥𝑠
The functions 𝑜𝑛𝑒𝑝𝑙𝑢𝑠, 𝑝𝑙𝑢𝑠𝑜𝑛𝑒, and 0 meet the conditions of the second duality theorem, as do 𝑠𝑛𝑜𝑐, 𝑐𝑜𝑛𝑠, and . We leave the verification as an exercise.
Hence the two definitions of 𝑙𝑒𝑛𝑔𝑡ℎ and reverse are equivalent on all finite lists.
It is not obvious whether there is any practical difference between the two definitions of 𝑙𝑒𝑛𝑔𝑡ℎ, but the second program for reverse is the more efficient of the two.
31. Earlier Sergei Winitzki implemented
digitsToInt as a function that did
not use recursion.
def digitsToInt(ds: Seq[Int]): Int = {
val n = ds.length
// Compute a sequence of powers of 10, e.g. [1000, 100, 10, 1].
val powers: Seq[Int] = (0 to n - 1).map(k => math.pow(10, n - 1 - k).toInt)
// Sum the powers of 10 with coefficients from ‘ds‘.
(ds zip powers).map { case (d, p) => d * p }.sum
}
def digitsToInt(s: Seq[Int]): Int = if (s.isEmpty) 0 else {
val x = s.last // To split s = xs :+ x, compute x
val xs = s.take(s.length - 1) // and xs.
digitsToInt(xs) * 10 + x // Call digitstoInt(...) recursively.
}
@tailrec def fromDigits(s: Seq[Int], res: Int = 0): Int =
// ‘res‘ is the accumulator.
if (s.isEmpty) res
else fromDigits(s.tail, 10 * res + s.head)
Then he reimplemented it as a recursive
function. Note that the function
processes digits from right to left.
Next he reimplemented it
as a tail-recursive function.
And later on, we’ll see that he’ll reimplement
it using a left fold. Note that the function
processes digits from left to right.
def digitsToInt(d: Seq[Int]): Int =
d.foldLeft(0){ (n, x) => n * 10 + x }
The second implementation can
be rewitten using a right fold.
def digitsToInt(d: Seq[Int]): Int =
d.foldRight(0){ (x, n) => n * 10 + x }
Why is it that the last two implementations produce the same results?
Note that the parameters of the lambda passed to foldLeft are in the
opposite order to those of the lambda passed to foldRight.
@philip_schwarz
32. The reason why d.foldLeft(0){ (n, x) => n * 10 + x } produces the same result as
d.foldRight(0){ (x, n) => n * 10 + x } (except when foldRight overflows the stack †), is
the existence of the third duality theorem.
Third duality theorem. For all finite lists 𝑥𝑠,
𝑓𝑜𝑙𝑑𝑟 𝑓 𝑒 𝑥𝑠 = 𝑓𝑜𝑙𝑑𝑙 𝑓𝑙𝑖𝑝 𝑓 𝑒 (𝑟𝑒𝑣𝑒𝑟𝑠𝑒 𝑥𝑠)
𝒘𝒉𝒆𝒓𝒆 𝑓𝑙𝑖𝑝 𝑓 𝑥 𝑦 = 𝑓 𝑦 𝑥
To illustrate the third duality theorem, consider
𝑓𝑜𝑙𝑑𝑟 ∶ 𝑥𝑠 = 𝑓𝑜𝑙𝑑𝑙 𝑓𝑙𝑖𝑝 (∶) [ ] (𝑟𝑒𝑣𝑒𝑟𝑠𝑒 𝑥𝑠)
Since 𝑓𝑜𝑙𝑑𝑟 ∶ = 𝑖𝑑 and 𝑓𝑜𝑙𝑑𝑙 𝑓𝑙𝑖𝑝 (∶) = [ ] 𝑟𝑒𝑣𝑒𝑟𝑠𝑒, we obtain
𝑥𝑠 = 𝑟𝑒𝑣𝑒𝑟𝑠𝑒 (𝑟𝑒𝑣𝑒𝑟𝑠𝑒 𝑥𝑠)
For all finite lists 𝑥𝑠, a result we have already proved directly.
def f(n: Int, x: Int): Int =
n * 10 + x
def flip[A,B,C](f: (A,B) => C): (B,A) => C =
(b, a) => f(a, b)
def digitsToIntl(d: List[Int]): Int =
foldl(f)(0)(d)
def digitsToIntr(d: List[Int]): Int =
foldr(flip(f))(0)(d.reverse)
assert(digitsToIntl(List(1,2,3,4,5)) == 12345)
assert(digitsToIntr(List(1,2,3,4,5)) == 12345)
† actually, in the case of the Scala standard library’s foldRight function, this proviso does not seem to apply – see the next slide.
33. def add(x: Int, y: Int): Int = x + y
def sumr(s: List[Int]): Int =
foldr(add)(0)(s)
def suml(s: List[Int]): Int =
foldl(add)(0)(s)
Remember, when we looked at the
first duality theorem, how the
implementation of sumr in terms of
foldr would crash if we passed it a
sufficiently large sequence, because
foldr is not tail-recursive and so
encounters a stack overflow error?
val oneTo40K = List.range(1,40_000)
assert( suml(oneTo40K) == 799_980_000)
assert(
try {
sumr(oneTo40K)
false
} catch {
case _:StackOverflowError => true
}
)
def sumL(s: List[Int]): Int =
s.foldLeft(0)(_+_)
def sumR(s: List[Int]): Int =
s.foldRight(0)(_+_)
assert( sumL(oneTo40K) == 799_980_000)
assert( sumR(oneTo40K) == 799_980_000)
Well, it turns out that there is no
stack overflow if we implement
sumr using the foldRight function in
the Scala standard library.
val oneTo1M = List.range(1,100_000)
assert( sumL(oneTo1M) == 1_783_293_664)
assert( sumR(oneTo1M) == 1_783_293_664)
34. def foldRight[B](z: B)(op: (A, B) => B): B =
reversed.foldLeft(z)((b, a) => op(a, b))
final override def foldRight[B](z: B)(op: (A, B) => B): B = {
var acc = z
var these: List[A] = reverse
while (!these.isEmpty) {
acc = op(these.head, acc)
these = these.tail
}
acc
}
override def foldLeft[B](z: B)(op: (B, A) => B): B = {
var acc = z
var these: LinearSeq[A] = coll
while (!these.isEmpty) {
acc = op(acc, these.head)
these = these.tail
}
acc
}
The reason is that the foldRight function is implemented by code that reverses the sequence,
flips the function that it is passed, and then calls foldLeft!
While this is not so obvious when we look at the code for foldRight
in List, because it effectively inlines the call to foldRight…
…it is plain to see in the
foldRight function for Seq
Third duality theorem. For all finite lists 𝑥𝑠,
𝑓𝑜𝑙𝑑𝑟 𝑓 𝑒 𝑥𝑠 = 𝑓𝑜𝑙𝑑𝑙 𝑓𝑙𝑖𝑝 𝑓 𝑒 (𝑟𝑒𝑣𝑒𝑟𝑠𝑒 𝑥𝑠)
𝒘𝒉𝒆𝒓𝒆 𝑓𝑙𝑖𝑝 𝑓 𝑥 𝑦 = 𝑓 𝑦 𝑥
This is the third duality
theorem in action
@philip_schwarz
35. def foldRight[A,B](as: List[A], z: B)(f: (A, B) => B): B =
as match {
case Nil => z
case Cons(x, xs) => f(x, foldRight(xs, z)(f))
}
Functional Programming in Scala
(by Paul Chiusano and Runar Bjarnason)
@pchiusano @runarorama
sealed trait List[+A]
case object Nil extends List[Nothing]
case class Cons[+A](head: A, tail: List[A]) extends List[A]
def foldRightViaFoldLeft[A,B](l: List[A], z: B)(f: (A,B) => B): B =
foldLeft(reverse(l), z)((b,a) => f(a,b))
@annotation.tailrec
def foldLeft[A,B](l: List[A], z: B)(f: (B, A) => B): B = l match{
case Nil => z
case Cons(h,t) => foldLeft(t, f(z,h))(f) }
Implementing foldRight via foldLeft is useful because it lets us implement
foldRight tail-recursively, which means it works even for large lists without overflowing
the stack.
Our implementation of foldRight is not tail-recursive and will result
in a StackOverflowError for large lists (we say it’s not stack-safe).
Convince yourself that this is the case, and then write another general list-
recursion function, foldLeft, that is tail-recursive
foldRight(Cons(1, Cons(2, Cons(3, Nil))), 0)((x,y) => x + y)
1 + foldRight(Cons(2, Cons(3, Nil)), 0)((x,y) => x + y)
1 + (2 + foldRight(Cons(3, Nil), 0)((x,y) => x + y))
1 + (2 + (3 + (foldRight(Nil, 0)((x,y) => x + y))))
1 + (2 + (3 + (0)))
6
At the bottom of this slide is where Functional
Programming in Scala shows that foldRight can be
defined in terms of foldLeft.
The third duality theorem in action.
36. And now, for completeness, we conclude Part 2 by looking
at some of Sergei Winitzki‘s solved foldLeft examples.
37. 2.2.5 Solved examples: using foldLeft
It is important to gain experience using the .foldLeft method.
Example 2.2.5.1 Use .foldLeft for implementing the max function for integer sequences. Return the special value Int.MinValue for
empty sequences.
Solution Write an inductive formulation of the max function:
• (Base case.) For an empty sequence, return Int.MinValue.
• (Inductive step.) If max is already computed on a sequence xs, say max(xs) = b, the value of max on a sequence xs :+ x is
math.max(b,x).
Now we can write the code:
def max(s: Seq[Int]): Int = s.foldLeft(Int.MinValue) { (b, x) => math.max(b, x) }
If we are sure that the function will never be called on empty sequences, we can implement max in a simpler way by using the
.reduce method:
def max(s: Seq[Int]): Int = s.reduce { (x, y) => math.max(x, y) }
Sergei Winitzki
sergei-winitzki-11a6431
38. Example 2.2.5.2 Implement the count method on sequences of type Seq[A].
Solution Using the inductive definition of the function count as shown in Section 2.2.1
Count the sequence elements satisfying a predicate p:
– for an empty sequence, Seq().count(p) == 0
– if xs.count(p) is known then (xs :+ x).count(p) == xs.count(p) + c, where we set c = 1
when p(x) == true and c = 0 otherwise
we can write the code as
def count[A](s: Seq[A], p: A => Boolean): Int =
s.foldLeft(0){ (b, x) => b + (if (p(x)) 1 else 0) }
Example 2.2.5.3 Implement the function digitsToInt using .foldLeft.
Solution The inductive definition of digitsToInt
• For an empty sequence of digits, Seq(), the result is 0. This is a convenient base case, even if we never call digitsToInt on an
empty sequence.
• If digitsToInt(xs) is already known for a sequence xs of digits, and we have a sequence xs :+ x with one more digit x,
then
is directly translated into code:
def digitsToInt(d: Seq[Int]): Int = d.foldLeft(0){ (n, x) => n * 10 + x }
Sergei Winitzki
sergei-winitzki-11a6431
39. def digitsToInt(d: Seq[Int]): Int =
d.foldLeft(0){ (n, x) => n * 10 + x }
Yes, this solution is the
one sketched out in Part 1.
40. Example 2.2.5.4 For a given non-empty sequence xs: Seq[Double], compute the minimum, the maximum,
and the mean as a tuple (xmin, xmax, xmean). … <skipping this one>
Example 2.2.5.5* Implement the function digitsToDouble using .foldLeft. The argument is of type Seq[Char]. As a test, the
expression digitsToDouble(Seq(’3’,’4’,’.’,’2’,’5’)) must evaluate to 34.25. Assume that all input characters are either
digits or a dot (so, negative numbers are not supported).
Solution The evaluation of a .foldLeft on a sequence of digits will visit the sequence from left to right. The updating function
should work the same as in digitsToInt until a dot character is found. After that, we need to change the updating function. So,
we need to remember whether a dot character has been seen. The only way for .foldLeft to “remember” any data is to hold that
data in the accumulator value. We can choose the type of the accumulator according to our needs. So, for this task we can choose
the accumulator to be a tuple that contains, for instance, the floating-point result constructed so far and a Boolean flag showing
whether we have already seen the dot character.
To see what digitsToDouble must do, let us consider how the evaluation of digitsToDouble(Seq(’3’,’4’,’.’,’2’,’5’))
should go. We can write a table showing the intermediate result at each iteration. This will hopefully help us figure out what the
accumulator and the updater function g(...) must be:
While the dot character was not yet seen, the updater function multiplies the previous result by 10 and adds the current digit. After
the dot character, the updater function must add to the previous result the current digit divided by a factor that represents
increasing powers of 10.
Current digit c Previous result n New result n’ = g(n,c)
‘3’ 0.0 3.0
‘4’ 3.0 34.0
‘.’ 34.0 34.0
‘2’ 34.0 34.2
‘5’ 34.2 34.25
Sergei Winitzki
sergei-winitzki-11a6431
41. In other words, the update computation nʹ = g(n, c) must be defined by these formulas:
1. Before the dot character: g(n, c) = n ∗ 10 + c.
2. After the dot character: g(n, c) = n + c/f , where f is 10, 100, 1000, ..., for each new digit.
The updater function g has only two arguments: the current digit and the previous accumulator value. So, the changing factor f
must be part of the accumulator value, and must be multiplied by 10 at each digit after the dot. If the factor f is not a part of the
accumulator value, the function g will not have enough information for computing the next accumulator value correctly. So, the
updater computation must be nʹ = g(n, c, f ), not nʹ = g(n, c).
For this reason, we choose the accumulator type as a tuple (Double, Boolean, Double) where the first number is the result n
computed so far, the Boolean flag indicates whether the dot was already seen, and the third number is f , that is, the power of 10
by which the current digit will be divided if the dot was already seen. Initially, the accumulator tuple will be equal to (0.0, false,
10.0). Then the updater function is implemented like this:
def update(acc: (Double, Boolean, Double), c: Char): (Double, Boolean, Double) =
acc match { case (n, flag, factor) =>
if (c == ’.’) (n, true, factor) // Set flag to ‘true‘ after a dot character was seen.
else {
val digit = c - ’0’
if (flag) // This digit is after the dot. Update ‘factor‘.
(n + digit/factor, flag, factor * 10)
else // This digit is before the dot.
(n * 10 + digit, flag, factor)
}
}
Sergei Winitzki
sergei-winitzki-11a6431
42. Now we can implement digitsToDouble as follows,
def digitsToDouble(d: Seq[Char]): Double = {
val initAccumulator = (0.0, false, 10.0)
val (n, _, _) =
d.foldLeft(initAccumulator)(update)
n
}
scala> digitsToDouble(Seq(’3’, ’4’, ’.’, ’2’,’5’))
res0: Double = 34.25
The result of calling d.foldLeft is a tuple (n, flag, factor), in which only the first part, n, is needed.
In Scala’s pattern matching expressions, the underscore symbol is used to denote the pattern variables whose values are not
needed in the subsequent code. We could extract the first part using the accessor method ._1, but the code will be more readable if
we show all parts of the tuple by writing (n, _, _).
Sergei Winitzki
sergei-winitzki-11a6431
43. That’s all for Part 2. I hope you enjoyed that.
There is still a plenty to cover, so I’ll see you in Part 3.