(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.
Scalaz provides an Applicative typeclass that can be used for the username and password validation program. It automatically supplies instances of <*> and *> for Validation, where the error type is a Semigroup. While Validation[String, Int] works, the error type for the validation program is actually a non-empty list of strings to prevent empty error messages. Scalaz provides a NonEmpty list datatype that has a Semigroup instance and represents a list that can never be empty, making it a better fit as an error type than a standard List.
Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala Part 2 ...Philip Schwarz
The document discusses implementing aggregation functions inductively using recursion or folding. It explains that aggregation functions like sum, count, max can be defined inductively with a base case for empty sequences and a recursive step to process additional elements. Implementing functions recursively risks stack overflow for long inputs, but tail-recursion and accumulator tricks can help avoid this. Folds provide an alternative to recursion for defining aggregations by processing sequences from left to right or right to left.
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
The document discusses factoring effects out of impure procedures in Scala. It shows an example of a contest function that originally coupled the logic for computing the winner with printing the output. This was refactored to separate the pure winner logic into its own function, while the contest function now handles just calling winner and printing the result. This illustrates a general technique of factoring an impure procedure into a pure core function and separate input/output handling functions to isolate the side effects.
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
The document discusses implementing various functional programming concepts like folding, scanning, and iteration to solve problems involving converting between digit sequences and integers. It provides examples in Scala and Haskell of using fold left to implement a digits-to-integer function and the iterate function to implement an integer-to-digits function. It also discusses using techniques like pipes in Scala and the $ operator in Haskell to improve readability by ordering function applications in the sequence they will be executed.
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
Function Composition - forward composition versus backward compositionPhilip Schwarz
The document discusses function composition in Scala. It defines forward composition as applying one function f to the result of another function g, written as f andThen g. Backward composition applies functions in the opposite order, first applying g and then f to the result, written as f compose g. Both operations can be implemented as curried functions in Scala, with andThen and compose respectively, that take the functions as arguments and return a function combining their operations.
(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.
Scala is a multi-paradigm programming language that blends object-oriented and functional programming. It is designed to express common programming patterns in a concise, elegant, and type-safe way. Scala runs on the Java Virtual Machine and interoperates seamlessly with Java, but also integrates concepts from languages such as Haskell, ML and Ruby. Some key features of Scala include support for functional programming, a static type system with type inference, pattern matching, actors and immutable data structures.
Some key features of Scala include:
1. It allows blending of functional programming and object-oriented programming for more concise and powerful code.
2. The static type system allows for type safety while maintaining expressiveness through type inference, implicits, and other features.
3. Scala code interoperates seamlessly with existing Java code and libraries due to its compatibility with the JVM.
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
The document discusses factoring effects out of impure procedures in Scala. It shows an example of a contest function that originally coupled the logic for computing the winner with printing the output. This was refactored to separate the pure winner logic into its own function, while the contest function now handles just calling winner and printing the result. This illustrates a general technique of factoring an impure procedure into a pure core function and separate input/output handling functions to isolate the side effects.
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
The document discusses implementing various functional programming concepts like folding, scanning, and iteration to solve problems involving converting between digit sequences and integers. It provides examples in Scala and Haskell of using fold left to implement a digits-to-integer function and the iterate function to implement an integer-to-digits function. It also discusses using techniques like pipes in Scala and the $ operator in Haskell to improve readability by ordering function applications in the sequence they will be executed.
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
Function Composition - forward composition versus backward compositionPhilip Schwarz
The document discusses function composition in Scala. It defines forward composition as applying one function f to the result of another function g, written as f andThen g. Backward composition applies functions in the opposite order, first applying g and then f to the result, written as f compose g. Both operations can be implemented as curried functions in Scala, with andThen and compose respectively, that take the functions as arguments and return a function combining their operations.
(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.
Scala is a multi-paradigm programming language that blends object-oriented and functional programming. It is designed to express common programming patterns in a concise, elegant, and type-safe way. Scala runs on the Java Virtual Machine and interoperates seamlessly with Java, but also integrates concepts from languages such as Haskell, ML and Ruby. Some key features of Scala include support for functional programming, a static type system with type inference, pattern matching, actors and immutable data structures.
Some key features of Scala include:
1. It allows blending of functional programming and object-oriented programming for more concise and powerful code.
2. The static type system allows for type safety while maintaining expressiveness through type inference, implicits, and other features.
3. Scala code interoperates seamlessly with existing Java code and libraries due to its compatibility with the JVM.
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.
This document provides an overview of functional programming in Scala. It begins with an introduction to functional programming basics like purity and referential transparency. It then covers functional data structures in Scala, including immutable lists. The document outlines topics on handling errors without exceptions, strict vs non-strict functions, purely functional state, and common FP structures like monoids and monads. Exercises are provided at the end to implement functions like tail, dropWhile, and foldLeft/foldRight on immutable lists.
How to start functional programming (in Scala): Day1Taisuke Oe
Functional programming involves composing computations like functions in a modular way. Scala supports both functional and object-oriented paradigms. Functions in Scala can be composed through methods like andThen and compose. Higher order functions allow functions to take other functions as arguments or return values. Pure functions always return the same output for the same inputs and avoid side effects. The Monoid typeclass abstracts the concepts of combining elements of a type and providing a default value, allowing new folding behaviors to be defined for types through implicit values. This allows behaviors to be extended to existing types without modifying them.
This document discusses loops in R and when to use them versus vectorization. It provides examples of for, while, and repeat loops in R. The key points are:
- Loops allow repeating operations but can be inefficient; vectorization is preferred when possible.
- For loops execute a fixed number of times based on an index or counter. Nested for loops allow manipulating multi-dimensional arrays.
- While and repeat loops execute until a condition is met, but repeat ensures at least one iteration.
- Break and next statements allow interrupting loop iterations early.
Fibonacci Function Gallery - Part 1 (of a series)Philip Schwarz
In this deck we are going to look at a number of different implementations of a function for computing the nth element of the Fibonacci sequence.
In part 1 we look at the following:
* Naïve Recursion
* Efficient Recursion with Tupling
* Tail Recursion with Accumulation
* Tail Recursion with Folding
* Stack-safe Recursion with Trampolining
Errata and scope for improvement (see later version of the deck for corrections):
* slide 6 - factorial should be fibonacci
* in some cases fib(0) = 0 and in others fib(0) = 1
This document provides an overview of a lecture on functional programming in Scala. It covers the following topics:
1. A recap of functional programming principles like functions as first-class values and no side effects.
2. An introduction to the Haskell programming language including its syntax for defining functions.
3. How functions are defined in Scala and how they are objects at runtime.
4. Examples of defining the factorial function recursively in Haskell and Scala, and making it tail recursive.
5. Concepts of first-class functions, currying, partial application, and an example of implementing looping in Scala using these techniques.
Folding Cheat Sheet #6 - sixth in a seriesPhilip Schwarz
Left and right folds and tail recursion.
Errata: there are some errors on slide 4. See here for a corrected versionsof the deck:
https://speakerdeck.com/philipschwarz/folding-cheat-sheet-number-6
https://fpilluminated.com/deck/227
This document discusses several techniques for optimizing C code:
1) Code motion involves moving code that is executed repeatedly in loops outside of the loop if its return value remains constant, such as calling a function.
2) Loop unrolling repeats the code within loops multiple times to reduce the total number of iterations and associated overhead.
3) Inlining replaces function calls with copies of the function code to avoid call overhead for simple functions.
Objectives Assignment 09 Applications of Stacks COS.docxdunhamadell
The document provides instructions for Assignment 09, which involves implementing four functions that use a stack data structure:
1. doParenthesisMatch() checks if a string of parentheses is properly matched and returns a boolean.
2. decodeIDSequence() decodes a string of 'I's and 'D's into a minimum number string without repeated digits.
3. insertItemOnSortedStack() inserts an item into a sorted stack.
4. sortStack() sorts an unsorted stack recursively.
Students are provided header and implementation files for a Stack ADT and tests, and must implement the functions in the given files while following style guidelines. The assignment evaluates correct implementation of the functions and stack usage,
This document discusses various techniques for optimizing R code performance, including profiling code to identify bottlenecks, vectorizing operations, avoiding copies, and byte code compilation. It provides examples demonstrating how to measure performance, compare alternative implementations, and apply techniques like doing less work, vectorization, and avoiding method dispatch overhead. The key message is that optimizing performance is an iterative process of measuring, testing alternatives, and applying strategies like these to eliminate bottlenecks.
The document discusses a Scala session on functions and higher-order functions. It provides examples of defining functions to calculate the square root of a number, sum values between ranges, and pass functions as parameters. Higher-order functions are introduced as functions that can take other functions as parameters or return functions.
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.
This document summarizes Lecture 1 of Real World Haskell. It introduces functional programming and Haskell, discusses the Haskell Platform and interactive interpreter ghci. It demonstrates defining simple functions and expressions, writing small interactive programs, and using recursion to number lines of text. Resources for learning more about Haskell are provided.
This document provides an overview of 14 labs covering topics in digital signal processing using MATLAB. The labs progress from basic introductions to MATLAB and signals and systems concepts to more advanced topics like filters, the z-transform, the discrete Fourier transform, image processing, and signal processing toolboxes. Lab 1 focuses on introducing basic MATLAB operations and functions for defining variables, vectors, matrices, and m-files.
The document discusses algorithmic complexity and the RAM model for analyzing computational efficiency. It explains that the RAM model treats memory as contiguous words that can be accessed and stored values in primitive operations. Common data structures like lists can be modeled in this way. The complexity of operations like concatenating lists, deleting elements, or extending lists is analyzed based on the number of primitive operations required. The document also covers analyzing best, average, and worst-case complexity and discusses common complexity classes like constant, logarithmic, linear, and quadratic time.
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')
List Unfolding - 'unfold' as the Computational Dual of 'fold', and how 'unfol...Philip Schwarz
In this deck we look at the following:
* how unfolding lists is the computational dual of folding lists
* different variants of the function for unfolding lists
* how they relate to the iterate function
Drawing Heighway’s Dragon - Part II - Recursive Function Simplification - Fro...Philip Schwarz
Drawing Heighway’s Dragon - Part II - Recursive Function Simplification - From 2^n Recursive Invocations To n Tail-Recursive Invocations Exploiting Self-Similarity.
Fibonacci Function Gallery - Part 2 - One in a seriesPhilip Schwarz
In this deck series we look at a number of different implementations of a function for computing the nth element of the Fibonacci sequence.
In part 2 we look at the following:
* Infinite Stream with Explicit Generation
* Infinite Stream with Implicit Definition
* Infinite Stream with Unfolding
* Infinite Stream with Iteration
* Infinite Stream with Scanning
Fibonacci Function Gallery - Part 1 (of a series) - with minor correctionsPhilip Schwarz
In this deck we are going to look at a number of different implementations of a function for computing the nth element of the Fibonacci sequence.
In part 1 we look at the following:
* Naïve Recursion
* Efficient Recursion with Tupling
* Tail Recursion with Accumulation
* Tail Recursion with Folding
* Stack-safe Recursion with Trampolining
The Debt Metaphor -Ward Cunningham in his 2009 YouTube videoPhilip Schwarz
This deck begins with a transcript of the YouTube video in which Ward Cunningham
* defines the Debt Metaphor (a term he coined)
* addresses the confusion he has noticed in some people’s understanding of the term
The deck continues with a visual summary of the video. The aim of the summary is twofold:
* provide a quick and easy reminder of the metaphor’s original definition
* help combat the semantic diffusion of the metaphor
Function Applicative for Great Good of Leap Year FunctionPhilip Schwarz
This deck is about the leap_year function shown in https://x.com/Iceland_jack/status/1802659835642528217, i.e.
leap_year :: Integral a => a -> Bool
leap_year = liftA2 (>) (gcd 80) (gcd 50)
Given an integer representing a year, the function returns a boolean indicating if that year is a leap year.
See why the Function Applicative allows the leap_year function to be defined as shown in that tweet.
Hand Rolled Applicative User ValidationCode KataPhilip Schwarz
Could you use a simple piece of Scala validation code (granted, a very simplistic one too!) that you can rewrite, now and again, to refresh your basic understanding of Applicative operators <*>, <*, *>?
The goal is not to write perfect code showcasing validation, but rather, to provide a small, rough-and ready exercise to reinforce your muscle-memory.
Despite its grandiose-sounding title, this deck consists of just three slides showing the Scala 3 code to be rewritten whenever the details of the operators begin to fade away.
The code is my rough and ready translation of a Haskell user-validation program found in a book called Finding Success (and Failure) in Haskell - Fall in love with applicative functors.
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"
Multiple Platforms of Unity Game Development.pdfNova Carter
Unity Game Development stands out for its unparalleled flexibility across multiple platforms, making it a top choice for developers aiming to reach a broad audience. With Unity, creators can build a game once and deploy it seamlessly across mobile devices, desktops, gaming consoles, web browsers, and even AR/VR systems. This multi-platform capability reduces development costs and effort while ensuring consistent performance and user experience across devices. Whether targeting casual mobile gamers or console enthusiasts, Unity empowers developers to scale their games effectively and maintain a competitive edge in today’s diverse gaming landscape.
How a Staff Augmentation Company IN USA Powers Flutter App Breakthroughs.pdfmary rojas
With local teams and talent aligned with U.S. business hours, a staff augmentation company in the USA enables real-time communication, faster decision-making, and better project coordination. This ensures smoother workflows compared to offshore-only models, especially for companies requiring tight collaboration.
Shortcomings of EHS Software – And How to Overcome ThemTECH EHS Solution
Shortcomings of EHS Software—and What Overcomes Them
What you'll learn in just 8 slides:
- 🔍 Why most EHS software implementations struggle initially
- 🚧 3 common pitfalls: adoption, workflow disruption, and delayed ROI
- 🛠️ Practical solutions that deliver long-term value
- 🔐 Key features: centralization, security, affordability
- 📈 Why the pros outweigh the cons
Perfect for HSE heads, plant managers, and compliance leads!
#EHS #TECHEHS #WorkplaceSafety #EHSCompliance #EHSManagement #ehssoftware #safetysoftware
Purple Box offers expert offensive security penetration testing to proactively identify and exploit weaknesses in your systems—just like a real attacker would. Our ethical hackers simulate advanced threats to uncover hidden vulnerabilities, test your defenses, and deliver actionable insights to strengthen your security posture.
Custom Software Development: Types, Applications and Benefits.pdfDigital Aptech
Discover the different types of custom software, their real-world applications across industries, and the key benefits they offer. Learn how tailored solutions improve efficiency, scalability, and business performance in this comprehensive overview.
BoxLang is the new CF-compatible server and CLI tool. It’s extensible easily with modules, which means you can write your own built in functions, tags, and more for your own use or to share with the community on ForgeBox. Let’s find out how.
Autoposting.ai Sales Deck - Skyrocket your LinkedIn's ROIUdit Goenka
1billion people scroll, only 1 % post…
That’s your opening to hijack LinkedIn—and Autoposting.ai is the unfair weapon Slideshare readers are hunting for…
LinkedIn drives 80 % of social B2B leads, converts 2× better than every other network, yet 87 % of pros still choke on the content hamster-wheel…
They burn 25 h a month writing beige posts, miss hot trends, then watch rivals scoop the deals…
Enter Autoposting.ai, the first agentic-AI engine built only for LinkedIn domination…
It spies on fresh feed data, cracks trending angles before they peak, and spins voice-perfect thought-leadership that sounds like you—not a robot…
Slides in play:
• 78 % average engagement lift in 90 days…
• 3.2× qualified-lead surge over manual posting…
• 42 % marketing time clawed back, week after week…
Real users report 5-8× ROI inside the first quarter, some crossing $1 M ARR six months faster…
Why does it hit harder than Taplio, Supergrow, generic AI writers?
• Taplio locks key features behind $149+ tiers… Autoposting gives you everything at $29…
• Supergrow churns at 20 % because “everyone” is no-one… Autoposting laser-targets • • LinkedIn’s gold-vein ICPs and keeps them glued…
• ChatGPT needs prompts, edits, scheduling hacks… Autoposting researches, writes, schedules—and optimizes send-time in one sweep…
Need social proof?
G2 reviews scream “game-changer”… Agencies slash content production 80 % and triple client capacity… CXOs snag PR invites and investor DMs after a single week of daily posts… Employee advocates hit 8× reach versus company pages and pump 25 % more SQLs into the funnel…
Feature bullets for the skim-reader:
• Agentic Research Engine—tracks 27+ data points, finds gaps your rivals ignore…
• Real Voice Match—your tone, slang, micro-jokes, intact…
• One-click Multiplatform—echo winning posts to Twitter, Insta, Facebook…
• Team Workspaces—spin up 10 seats without enterprise red tape…
• AI Timing—drops content when your buyers actually scroll, boosting first-hour velocity by up to 4×…
Risk? Zero…
Free 7-day trial, 90-day results guarantee—hit 300 % ROI or walk away… but the clock is ticking while competitors scoop your feed…
So here’s the ask:
Swipe down, smash the “Download” or “Try Now” button, and let Autoposting.ai turn Slideshare insights into pipeline—before today’s trending topic vanishes…
The window is open… How loud do you want your LinkedIn megaphone?
A Claims Processing System enhances customer satisfaction, efficiency, and compliance by automating the claims lifecycle—enabling faster settlements, fewer errors, and greater transparency. Explore More - https://www.damcogroup.com/insurance/claims-management-software
Menu in Android (Define,Create,Inflate and Click Handler)Nabin Dhakal
In Android, a **menu** provides options for user actions and navigation in an app. Menus can appear as **options menus** (accessed via the app bar), **context menus** (triggered by long-press), or **popup menus** (small floating lists). They are typically defined in XML using `<menu>` and `<item>` tags and inflated using `MenuInflater` in activities or fragments. Developers handle menu item clicks using `onOptionsItemSelected()` or similar methods. Menus help improve usability by grouping actions in a consistent interface. Common use cases include settings, search, and sharing options, offering a clean and accessible way to enhance app functionality.
Menus in Android offer a consistent and user-friendly way to present actions and navigation options within an app. By using options menus for global actions, context menus for specific UI elements, and popup menus for flexible interaction, developers can enhance the overall usability and functionality of their applications. Proper implementation of menus not only organizes actions effectively but also improves the user experience by making key features easily accessible.
This presentation provides an overview of Agentic AI, intelligent systems capable of autonomous decision-making and action with minimal supervision. It highlights how these systems interact with their environment, learn over time, and adapt to complex situations. The content outlines the transformative potential of Agentic AI across industries and emphasizes the importance of understanding its core features and challenges.
And overview of Nasdanika Models and their applicationsPavel Vlasov
This presentation provides an overview of Nasdanika metamodels and their applications - reference documentation, analysis, code generation, use with GenAI operating on complex structures instead of text - humans don't think in text, they think in images (diagrams) - objects and their relationships. Translating human thoughts to text is an "expensive" and error prone process. And this is where diagramming, modeling, and generation of textual description from a model can help humans and GenAI to communicate better.
Frontier AI Regulation: What form should it take?Petar Radanliev
Frontier AI systems, including large-scale machine learning models and autonomous decision-making technologies, are deployed across critical sectors such as finance, healthcare, and national security. These present new cyber-risks, including adversarial exploitation, data integrity threats, and legal ambiguities in accountability. The absence of a unified regulatory framework has led to inconsistencies in oversight, creating vulnerabilities that can be exploited at scale. By integrating perspectives from cybersecurity, legal studies, and computational risk assessment, this research evaluates regulatory strategies for addressing AI-specific threats, such as model inversion attacks, data poisoning, and adversarial manipulations that undermine system reliability. The methodology involves a comparative analysis of domestic and international AI policies, assessing their effectiveness in managing emerging threats. Additionally, the study explores the role of cryptographic techniques, such as homomorphic encryption and zero-knowledge proofs, in enhancing compliance, protecting sensitive data, and ensuring algorithmic accountability. Findings indicate that current regulatory efforts are fragmented and reactive, lacking the necessary provisions to address the evolving risks associated with frontier AI. The study advocates for a structured regulatory framework that integrates security-first governance models, proactive compliance mechanisms, and coordinated global oversight to mitigate AI-driven threats. The investigation considers that we do not live in a world where most countries seem to be wishing to follow our ideals, for various reasons (competitiveness, geo-political dominations, hybrid warfare, loss of attractiveness of the European model in the Big South, etc.), and in the wake of this particular trend, this research presents a regulatory blueprint that balances technological advancement with decentralised security enforcement (i.e., blockchain).
The Engineering, Procurement, and Construction (EPC) industry is highly complex, involving multiple stakeholders, high-value procurement, strict timelines, and resource-heavy project execution. In such a demanding environment, using the right ERP system is not a luxury—it's a necessity.
This presentation highlights the Top 5 Odoo ERP modules specifically tailored to meet the dynamic needs of the EPC sector. Whether you're managing large-scale infrastructure projects or specialized engineering contracts, Odoo provides an integrated solution that can streamline your entire project lifecycle.
🔍 What’s Inside:
Key challenges faced by EPC companies
Overview of essential Odoo modules
Real-world benefits of using Project, Purchase, Inventory, Field Service, and Accounting modules
How these modules contribute to cost control, real-time visibility, and operational efficiency
This presentation is designed for EPC business owners, project managers, procurement heads, and field service teams who are exploring digital transformation through Odoo ERP.
Insurance broker software enables brokers to streamline and simplify client management. It is a comprehensive solution to boost productivity and consolidate business data. Let’s have a look at the features that every good insurance broking software must possess. Explore more - https://www.damcogroup.com/insurance/brokeredge-broker-management-software
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.