This international bestseller has been revised with new exercises, annotations, and full coverage of Scala 3.
In Functional Programming in Scala, Second Edition you will learn how to
Recognize and write purely functional code
Work with errors without using exceptions
Work with state and concurrency
Interact with functional structures that define common behaviors
Write code that performs I/O without sacrificing functional programming
Functional Programming in Scala has helped over 30,000 developers discover the power of functional programming. You’ll soon see why reviewers have called it “mindblowing”! The book smooths the complexity curve of functional programming, making it simple to understand the basics and intuitive to progress to more advanced topics. Concrete examples and exercises show you FP in the real world and reveal how it can improve your everyday coding practices. This second edition comes packed with the latest standards of FP, as well as full code updates to Scala 3, and its new language features.
About the Technology
Functional code is easy to test, reuse, and parallelize, and it’s practically immune to whole categories of state-related bugs. With its strong functional features, familiar syntax, and seamless interoperability with Java, there’s no better place to start learning functional programming than the flexible Scala language.
Functional programming (FP) is based on a simple premise with far-reaching implications: we construct our programs using only pure functions —in other words, functions that have no side effects. But what are side effects? A function has a side effect if it does something other than simply return a result. This includes, for example, the following cases:
• Modifying a variable
• Modifying a data structure in place
• Setting a field on an object
• Throwing an exception or halting with an error
• Printing to the console or reading user input
• Reading from or writing to a file
• Drawing on the screen
About the Book
In Functional Programming in Scala, Second Edition you’ll learn functional programming from first principles. Hands-on exercises and examples make it easy to start thinking and coding functionally. This revised edition contains extensive exercise annotations to help you explore FP in depth, along with steps to build your own functional libraries in Scala. Once the functional lightbulb goes on, you’ll never look at coding the same way again.
What's Inside
Recognize and write purely functional code
Work with errors without using exceptions
Work with state and concurrency
Interact with functional structures that define common behaviors
Author(s): Michael Pilquist, Rúnar Bjarnason; Paul Chiusano
Edition: 2
Publisher: Manning Publications
Year: 2023
Language: English
Pages: 488
Praise for the First Edition
Functional Programming in Scala, Second Edition
Copyright
contents
front matter
foreword to the first edition
foreword to the second edition
preface to the second edition
acknowledgments
about this book
How this book is structured
Audience
How to read this book
Code conventions and downloads
Setting expectations
liveBook discussion forum
about the authors
Part 1. Introduction to functional programming
1 What is functional programming?
1.1 Understanding the benefits of functional programming
1.1.1 A program with side effects
1.1.2 A functional solution: Removing the side effects
1.2 Exactly what is a (pure) function?
1.3 Referential transparency, purity, and the substitution model
1.4 Conclusion
Summary
2 Getting started with functional programming in Scala
2.1 Introducing Scala the language
2.1.1 Running our program
2.2 Objects and namespaces
2.3 Higher-order functions: Passing functions to functions
2.3.1 A short detour: Writing loops functionally
2.3.2 Writing our first higher-order function
2.4 Polymorphic functions: Abstracting over types
2.4.1 An example of a polymorphic function
2.4.2 Calling higher-order functions with anonymous functions
2.5 Following types to implementations
2.6 Conclusion
Summary
2.7 Exercise answers
3 Functional data structures
3.1 Defining functional data structures
3.2 Pattern matching
3.3 Data sharing in functional data structures
3.3.1 The efficiency of data sharing
3.3.2 Recursion over lists and generalizing to higher-order functions
3.3.3 More functions for working with lists
3.3.4 Loss of efficiency when assembling list functions from simpler components
3.4 Trees
3.5 Conclusion
Summary
3.6 Exercise answers
4 Handling errors without exceptions
4.1 The good and bad aspects of exceptions
4.2 Possible alternatives to exceptions
4.3 The Option data type
4.3.1 Usage patterns for Option
4.3.2 Option composition, lifting, and wrapping exception-oriented APIs
4.4 The Either data type
4.4.1 Accumulating errors
4.4.2 Extracting a Validated type
4.5 Conclusion
Summary
4.6 Exercise answers
5 Strictness and laziness
5.1 Strict and nonstrict functions
5.2 Lazy lists: An extended example
5.2.1 Memoizing lazy lists and avoiding recomputation
5.2.2 Helper functions for inspecting lazy lists
5.3 Separating program description from evaluation
5.4 Infinite lazy lists and corecursion
5.5 Conclusion
Summary
5.6 Exercise answers
6 Purely functional state
6.1 Generating random numbers using side effects
6.2 Purely functional random number generation
6.3 Making stateful APIs pure
6.4 A better API for state actions
6.4.1 Combining state actions
6.4.2 Nesting state actions
6.5 A general state action data type
6.6 Purely functional imperative programming
6.7 Conclusion
Summary
6.8 Exercise Answers
Part 2. Functional design and combinator libraries
7 Purely functional parallelism
7.1 Choosing data types and functions
7.1.1 A data type for parallel computations
7.1.2 Combining parallel computations
7.1.3 Explicit forking
7.2 Picking a representation
7.2.1 Refining the API
7.3 The algebra of an API
7.3.1 The law of mapping
7.3.2 The law of forking
7.3.3 Breaking the law: A subtle bug
7.3.4 A fully non-blocking Par implementation using actors
7.4 Refining combinators to their most general form
7.5 Conclusion
Summary
7.9 Exercise answers
8 Property-based testing
8.1 A brief tour of property-based testing
8.1.1 Choosing data types and functions
8.1.2 Initial snippets of an API
8.1.3 The meaning and API of properties
8.1.4 The meaning and API of generators
8.1.5 Generators that depend on generated values
8.1.6 Refining the Prop data type
8.2 Test case minimization
8.2.1 Using the library and improving its usability
8.2.2 Some simple examples
8.2.3 Writing a test suite for parallel computations
8.3 Testing higher-order functions and future directions
8.4 The laws of generators
8.5 Conclusion
Summary
8.6 Exercise answers
9 Parser combinators
9.1 Designing an algebra first
9.2 A possible algebra
9.2.1 Slicing and nonempty repetition
9.3 Handling context sensitivity
9.4 Writing a JSON parser
9.4.1 The JSON format
9.4.2 A JSON parser
9.5 Error reporting
9.5.1 A possible design
9.5.2 Error nesting
9.5.3 Controlling branching and backtracking
9.6 Implementing the algebra
9.6.1 One possible implementation
9.6.2 Sequencing parsers
9.6.3 Labeling parsers
9.6.4 Failover and backtracking
9.6.5 Context-sensitive parsing
9.7 Conclusion
Summary
9.8 Exercise answers
Part 3. Common structures in functional design
10 Monoids
10.1 What is a monoid?
10.2 Folding lists with monoids
10.3 Associativity and parallelism
10.4 Example: Parallel parsing
10.5 Typeclasses
10.6 Foldable data structures
10.7 Composing monoids
10.7.1 Assembling more complex monoids
10.7.2 Using composed monoids to fuse traversals
10.8 Conclusion
Summary
10.9 Exercise answers
11 Monads
11.1 Functors: Generalizing the map function
11.1.1 Functor laws
11.2 Monads: Generalizing the flatMap and unit functions
11.2.1 The Monad trait
11.3 Monadic combinators
11.4 Monad laws
11.4.1 The associative law
11.4.2 Proving the associative law for a specific monad
11.4.3 The identity laws
11.5 Just what is a monad?
11.5.1 The identity monad
11.5.2 The State monad and partial type application
11.6 Conclusion
Summary
11.7 Exercise answers
12 Applicative and traversable functors
12.1 Generalizing monads
12.2 The Applicative trait
12.3 The difference between monads and applicative functors
12.3.1 The Option applicative versus the Option monad
12.3.2 The Parser applicative versus the Parser monad
12.4 The advantages of applicative functors
12.4.1 Not all applicative functors are monads
12.5 The applicative laws
12.5.1 Left and right identity
12.5.2 Associativity
12.5.3 Naturality of product
12.6 Traversable functors
12.7 Uses of Traverse
12.7.1 From monoids to applicative functors
12.7.2 Traversals with State
12.7.3 Combining traversable structures
12.7.4 Traversal fusion
12.7.5 Nested traversals
12.7.6 Monad composition
12.8 Conclusion
Summary
12.9 Exercise answers
Part 4. Effects and I/O
13 External effects and I/O
13.1 Factoring effects
13.2 A simple IO type
13.2.1 Handling input effects
13.2.2 Benefits and drawbacks of the simple IO type
13.3 Avoiding the StackOverflowError
13.3.1 Reifying control flow as data constructors
13.3.2 Trampolining: A general solution to stack overflow
13.4 A more nuanced I/O type
13.4.1 Free monads
13.4.2 A monad that supports only console I/O
13.4.3 Pure interpreters
13.5 Non-blocking and asynchronous I/O
13.5.1 Composing free algebras
13.6 Capabilities
13.7 A general-purpose I/O type
13.7.1 The main program at the end of the universe
13.8 Why the IO type is insufficient for streaming I/O
13.9 Conclusion
Summary
13.10 Exercise answers
14 Local effects and mutable state
14.1 Purely functional mutable state
14.2 A data type to enforce the scoping of side effects
14.2.1 A little language for scoped mutation
14.2.2 An algebra of mutable references
14.2.3 Running mutable state actions
14.2.4 Mutable arrays
14.2.5 A purely functional in-place quicksort
14.3 Purity is contextual
14.3.1 What counts as a side effect?
14.4 Conclusion
Summary
14.5 Exercise answers
15 Stream processing and incremental I/O
15.1 Problems with imperative I/O: An example
15.2 Simple stream transformations
15.2.1 Creating pulls
15.2.2 Composing stream transformations
15.2.3 Processing files
15.3 Extensible pulls and streams
15.3.1 Effectful streaming computations
15.3.2 Handling errors
15.3.3 Ensuring resource safety
15.3.4 Dynamic resource allocation
15.4 Applications
15.5 Conclusion
Summary
15.6 Exercise answers
index