Grokking Functional Programming
Part 1 the functional toolkit . . . . . . . . . . . . . . . . . . .1
1 Learning functional programming 003
- 01 001 004 Perhaps you picked up this book because...
- 02 002 005 What do you need to know before we start?
- 03 003 006 What do functions look like?
- 04 004 007 Meet the function
- 05 005 008 When the code lies...
- 06 006 009 Imperative vs. declarative
- 07 007 010 Coffee break: Imperative vs. declarative
- 08 008 011 Coffee break explained: Imperative vs. declarative
- 09 009 012 How useful is learning functional programming?
- 10 010 013 Leaping into Scala
- 11 011 014 Practicing functions in Scala
- 12 012 015 Getting your tools ready
- 13 013 016 Getting to know the REPL
- 14 014 017 Writing your first functions!
- 15 015 018 How to use this book
2 Pure functions 21
- 01 016 022 Why do we need pure functions?
- 02 017 023 Coding imperatively
- 03 018 024 Breaking the code
- 04 019 025 Passing copies of the data
- 05 020 026 Breaking the code...again
- 06 021 027 Recalculating instead of storing
- 07 022 028 Focusing on the logic by passing the state
- 08 023 029 Where did the state go?
- 09 024 030 The difference between impure and pure functions
- 10 025 031 Coffee break: Refactoring to a pure function
- 11 026 032 Coffee break explained: Refactoring to a pure function
- 12 027 034 In pure functions we trust
- 13 028 035 Pure functions in programming languages
- 14 029 036 Difficulty of staying pure...
- 15 030 037 Pure functions and clean code
- 16 031 038 Coffee break: Pure or impure?
- 17 032 039 Coffee break explained: Pure or impure?
- 18 033 040 Using Scala to write pure functions
- 19 034 041 Practicing pure functions in Scala
- 20 035 042 Testing pure functions
- 21 036 043 Coffee break: Testing pure functions
- 22 037 044 Coffee break explained: Testing pure functions
3 Immutable values 47
- 01 038 048 The fuel for the engine
- 02 039 049 Another case for immutability
- 03 040 050 Can you trust this function?
- 04 041 051 Mutability is dangerous
- 05 042 052 Functions that lie...again
- 06 043 053 Fighting mutability by working with copies
- 07 044 054 Coffee break: Getting burned by mutability
- 08 045 055 Coffee break explained: Getting burned by mutability
- 09 046 058 Introducing shared mutable state
- 10 047 059 State’s impact on programming abilities
- 11 048 060 Dealing with the moving parts
- 12 049 061 Dealing with the moving parts using FP
- 13 050 062 Immutable values in Scala
- 14 051 063 Building our intuition about immutability
- 15 052 064 Coffee break: The immutable String API
- 16 053 065 Coffee break explained: The immutable String API
- 17 054 066 Hold on...Isn’t this bad?
- 18 055 067 Purely functional approach to shared mutable state
- 19 056 069 Practicing immutable slicing and appending
4 Functions as values 071
- 01 057 072 Implementing requirements as functions
- 02 058 073 Impure functions and mutable values strike back
- 03 059 074 Using Java Streams to sort the list
- 04 060 075 Function signatures should tell the whole story
- 05 061 077 Changing requirements 76 We just pass the code around!
- 06 062 078 Using Java’s Function values
- 07 063 079 Using the Function syntax to deal with code duplication
- 08 064 080 Passing user-defined functions as arguments
- 09 065 081 Coffee break: Functions as parameters
- 10 066 082 Coffee break explained: Functions as parameters
- 11 067 083 Problems with reading functional Java
- 12 068 084 Passing functions in Scala
- 13 069 085 Deep dive into sortBy
- 14 070 086 Signatures with function parameters in Scala
- 15 071 087 Passing functions as arguments in Scala
- 16 072 088 Practicing function passing
- 17 073 089 Embracing declarative programming
- 18 074 090 Passing functions to custom-made functions
- 19 075 091 Small functions and their responsibilities
- 20 076 092 Passing functions inline
- 21 077 093 Coffee break: Passing functions in Scala
- 22 078 094 Coffee break explained: Passing functions in Scala
- 23 079 095 What else can we achieve just by passing functions?
- 24 080 096 Applying a function to each element of a list
- 25 081 097 Applying a function to each element of a list using map
- 26 082 098 Getting to know map
- 27 083 099 Practicing map
- 28 084 100 Learn once, use everywhere
- 29 085 101 Returning parts of the list based on a condition
- 30 086 102 Returning parts of the list using filter
- 31 087 103 Getting to know filter
- 32 088 104 Practicing filter
- 33 089 105 Our journey so far...
- 34 090 106 Don’t repeat yourself?
- 35 091 107 Is my API easy to use?
- 36 092 108 Adding a new parameter is not enough
- 37 093 109 Functions can return functions
- 38 094 110 Using functions that can return functions
- 39 095 111 Functions are values
- 40 096 112 Coffee break: Returning functions
- 41 097 113 Coffee break explained: Returning functions
- 42 098 114 Designing functional APIs
- 43 099 115 Iterative design of functional APIs
- 44 100 116 Returning functions from returned functions
- 45 101 117 How to return functions from returned functions
- 46 102 118 Using the flexible API built with returned functions
- 47 103 119 Using multiple parameter lists in functions
- 48 104 120 We have been currying!
- 49 105 121 Practicing currying
- 50 106 122 Programming by passing function values
- 51 107 123 Reducing many values into a single value
- 52 108 124 Reducing many values into a single one using foldLeft
- 53 109 125 Getting to know foldLeft
- 54 110 126 foldLeft must-knows
- 55 111 127 Practicing foldLeft
- 56 112 128 Modeling immutable data
- 57 113 129 Using product types with higher-order functions
- 58 114 130 More concise syntax for inline functions
Part 2 functional Programs . . . . . . . . . . . . . . . . . . . .133
5 Sequential programs 135
- 01 115 136 Writing pipeline-based algorithms
- 02 116 137 Composing larger programs from smaller pieces
- 03 117 138 The imperative approach
- 04 118 139 flatten and flatMap
- 05 119 140 Practical use case of using more flatMaps
- 06 120 141 flatMap and changing the size of the list
- 07 121 142 Coffee break: Dealing with lists of lists
- 08 122 143 Coffee break explained: Dealing with lists of lists
- 09 123 144 Chained flatMaps and maps
- 10 124 145 Nested flatMaps
- 11 125 146 Values that depend on other values
- 12 126 147 Practicing nested flatMaps
- 13 127 148 A better syntax for nested flatMaps
- 14 128 149 For comprehensions to the rescue!
- 15 129 150 Coffee break: flatMaps vs. for comprehensions
- 16 130 151 Coffee break explained: flatMaps vs. for comprehensions
- 17 131 152 Getting to know for comprehensions
- 18 132 153 It’s not the for you are looking for!
- 19 133 154 Inside a for comprehension
- 20 134 155 More sophisticated for comprehensions
- 21 135 156 Checking all combinations using a for comprehension
- 22 136 157 Filtering techniques
- 23 137 158 Coffee break: Filtering techniques
- 24 138 159 Coffee break explained: Filtering techniques
- 25 139 160 Looking for a greater abstraction
- 26 140 161 Comparing map, foldLeft, and flatMap
- 27 141 162 Using for comprehensions with Sets
- 28 142 163 Using for comprehensions with many types
- 29 143 164 Practicing for comprehensions
- 30 144 165 Defining for comprehensions...again
- 31 145 166 Using for comprehensions with noncollection types
- 32 146 167 Avoiding nulls: Option type
- 33 147 168 Parsing as a pipeline
- 34 148 169 Coffee break: Parsing with Option
- 35 149 170 Coffee break explained: Parsing with Option
6 Error handling 173
- 01 150 174 Handling lots of different errors, gracefully
- 02 151 175 Is it even possible to handle them all?
- 03 152 176 Sort the list of TV shows by their running time
- 04 153 177 Implementing the sorting requirement
- 05 154 178 Dealing with data coming from the outside world
- 06 155 179 Functional design: Building from small blocks
- 07 156 180 Parsing Strings into immutable objects
- 08 157 181 Parsing a List is just parsing one element
- 09 158 182 Parsing a String into a TvShow
- 10 159 183 What about potential errors?
- 11 160 184 Is returning null a good idea?
- 12 161 185 How do we handle potential errors more gracefully?
- 13 162 186 Implementing a function that returns an Option
- 14 163 187 Option forces us to handle possible errors
- 15 164 188 Building from small blocks
- 16 165 189 Functional design is building from small blocks
- 17 166 190 Writing a small, safe function that returns an Option
- 18 167 192 Functions, values, and expressions
- 19 168 193 Practicing safe functions that return Options
- 20 169 194 How do errors propagate?
- 21 170 195 Values represent errors
- 22 171 196 Option, for comprehensions, and checked exceptions...
- 23 172 197 What about checked exceptions?
- 24 173 198 Conditional recovery
- 25 174 199 Conditional recovery using the imperative style
- 26 175 201 Checked exceptions don’t compose—Options do!
- 27 176 202 How does orElse work?
- 28 177 203 Practicing functional error handling
- 29 178 204 Functions compose, even in the presence of errors
- 30 179 205 Compiler reminds us that errors need to be covered
- 31 180 206 Compilation errors are good for us!
- 32 181 207 Transforming a List of Options into a flat List
- 33 182 208 Let the compiler be our guide...
- 34 183 209 ...but let’s not trust the compiler too much!
- 35 184 210 Coffee break: Error-handling strategies
- 36 185 211 Coffee break explained: Error-handling strategies
- 37 186 212 Two different error-handling strategies
- 38 187 213 All-or-nothing error-handling strategy
- 39 188 214 Folding a List of Options into an Option of a List
- 40 189 215 We now know how to handle multiple possible errors!
- 41 190 216 How to know what failed
- 42 191 217 We need to convey error details in the return value
- 43 192 218 Conveying error details using Either
- 44 193 219 Refactoring to Either
- 45 194 220 Returning an Either instead of an Option
- 46 195 223 Practicing safe functions that return Either
- 47 196 224 What we learned about Option works with Either
- 48 197 225 Coffee break: Error handling using Either
- 49 198 226 Coffee break explained: Error handling using Either
- 50 199 227 Working with Option/Either
7 Requirements as types 229
- 01 200 230 Modeling data to minimize programmers’ mistakes
- 02 201 231 Well-modeled data can’t lie
- 03 202 232 Designing using what we know so far (which is primitive types)
- 04 203 233 Using data modeled as primitive types
- 05 204 234 Coffee break: The pain of primitive types
- 06 205 235 Coffee break explained: The pain of primitive types
- 07 206 236 Problems with the primitive type approach to modeling
- 08 207 237 Using primitive types makes our jobs harder!
- 09 208 238 Newtypes protect against misplaced parameters
- 10 209 239 Using newtypes in data models
- 11 210 240 Practicing newtypes
- 12 211 241 Making sure only valid data combinations are possible
- 13 212 242 Modeling possibility of absence in your data
- 14 213 243 Changes in the model force changes in the logic
- 15 214 244 Using data modeled as Options in your logic
- 16 215 245 Higher-order functions for the win!
- 17 216 246 There is probably a higher-order function for that!
- 18 217 247 Coffee break: forall/exists/contains
- 19 218 248 Coffee break explained: forall/exists/contains
- 20 219 249 Coupling a concept inside a single product type
- 21 220 250 Modeling finite possibilities
- 22 221 251 Using sum types
- 23 222 252 Even better modeling with sum types
- 24 223 253 Using the sum type + product type combo
- 25 224 254 Product types + sum types = algebraic data types (ADTs)
- 26 225 255 Using ADT-based models in behaviors (functions)
- 27 226 256 Destructuring ADTs using pattern matching
- 28 227 257 Duplication and DRY
- 29 228 258 Practicing pattern matching
- 30 229 259 Newtypes, ADTs, and pattern matching in the wild
- 31 230 260 What about inheritance?
- 32 231 261 Coffee break: Functional data design
- 33 232 262 Coffee break explained: Functional data design
- 34 233 263 Modeling behaviors
- 35 234 264 Modeling behaviors as data
- 36 235 265 Implementing functions with ADT-based parameters
- 37 236 266 Coffee break: Design and maintainability
- 38 237 267 Coffee break explained: Design and maintainability
8 IO as values 269
- 01 238 270 Talking to the outside world
- 02 239 271 Integrating with an external API
- 03 240 272 Properties of a side-effectful IO action
- 04 241 273 Imperative solution to side-effecting IO code
- 05 242 274 Problems with the imperative approach to IO
- 06 243 275 Can we really do better using FP?
- 07 244 276 Doing IO vs. using IO’s result
- 08 245 277 Handling IO imperatively
- 09 246 278 Computations as IO values
- 10 247 279 IO values
- 11 248 280 IO values in the wild
- 12 249 281 Pushing the impurity out
- 13 250 282 Using values fetched from two IO actions
- 14 251 283 Combining two IO values into a single IO value
- 15 252 284 Practicing creating and combining IO values
- 16 253 285 Disentangling concerns by working with values only
- 17 254 286 The IO type is viral
- 18 255 287 Coffee break: Working with values
- 19 256 288 Coffee break explained: Working with values
- 20 257 289 Toward functional IO
- 21 258 290 What about IO failures?
- 22 259 291 Running a program described by IO may fail!
- 23 260 292 Remember orElse?
- 24 261 293 Lazy and eager evaluation
- 25 262 294 Implementing recovery strategies using IO.orElse
- 26 263 295 Implementing fallbacks using orElse and pure
- 27 264 296 Practicing failure recovery in IO values
- 28 265 297 Where should we handle potential failures?
- 29 266 298 Toward functional IO with failure handling
- 30 267 299 Pure functions don’t lie, even in the unsafe world!
- 31 268 300 Functional architecture
- 32 269 301 Using IO to store data
- 33 270 304 Coffee break: Using IO to store data
- 34 271 305 Coffee break explained: Using IO to store data
- 35 272 306 Treating everything as values
- 36 273 307 Treating retries as values
- 37 274 309 Treating an unknown number of API calls as values
- 38 275 311 Practicing functional signature intuitions
9 Streams as values 313
- 01 276 314 To infinity and beyond
- 02 277 315 Dealing with an unknown number of values
- 03 278 316 Dealing with external impure API calls (again)
- 04 279 317 The functional approach to the design
- 05 280 318 Immutable maps
- 06 281 319 Practicing immutable maps
- 07 282 320 How many IO calls should we make?
- 08 283 321 The bottom-up design
- 09 284 322 Advanced list operations
- 10 285 323 Introducing tuples
- 11 286 324 Zipping and dropping
- 12 287 325 Pattern matching on tuples
- 13 288 326 Coffee break: Working with maps and tuples
- 14 289 327 Coffee break explained: Working with maps and tuples
- 15 290 328 Functional jigsaw puzzle
- 16 291 329 Following types in a bottom-up design
- 17 292 330 Prototyping and dead ends
- 18 293 331 Recursive functions
- 19 294 332 Infinity and laziness
- 20 295 333 Recursive function structure
- 21 296 334 Dealing with an absence in the future (using recursion)
- 22 297 335 Usefulness of infinite recursive calls
- 23 298 336 Coffee break: Recursion and infinity
- 24 299 337 Coffee break explained: Recursion and infinity
- 25 300 338 Creating different IO programs using recursion
- 26 301 339 Using recursion to make an arbitrary number of calls
- 27 302 340 Problems with the recursive version
- 28 303 341 Introducing data streams
- 29 304 342 Streams in imperative languages
- 30 305 343 Values on demand
- 31 306 344 Stream processing, producers, and consumers
- 32 307 345 Streams and IO
- 33 308 346 The functional Stream
- 34 309 347 Streams in FP are values
- 35 310 348 Streams are recursive values
- 36 311 349 Primitive operations and combinators
- 37 312 350 Streams of IO-based values
- 38 313 351 Infinite streams of IO-based values
- 39 314 352 Executing for side effects
- 40 315 353 Practicing stream operations
- 41 316 354 Using streams to our advantage
- 42 317 355 Infinite stream of API calls
- 43 318 356 Handling IO failures in streams
- 44 319 357 Separated concerns
- 45 320 358 Sliding windows
- 46 321 360 Waiting between IO calls
- 47 322 361 Zipping streams
- 48 323 362 Benefits of using the stream-based approach
10 Concurrent Programs 365
- 01 324 366 Threads, threads everywhere
- 02 325 367 Declarative concurrency
- 03 326 368 Sequential vs. concurrent
- 04 327 370 Coffee break: Sequential thinking
- 05 328 370 Coffee break explained: Sequential thinking
- 06 329 371 The need for batching
- 07 330 372 Batching implementation
- 08 331 373 The concurrent world
- 09 332 374 The concurrent state
- 10 333 375 Imperative concurrency
- 11 334 377 Atomic references
- 12 335 378 Introducing Ref
- 13 336 379 Updating Ref values
- 14 337 380 Using Ref values
- 15 338 381 Making it all concurrent
- 16 339 382 parSequence in action
- 17 340 384 Practicing concurrent IOs
- 18 341 385 Modeling concurrency
- 19 342 386 Coding using Refs and fibers
- 20 343 388 IOs that run infinitely
- 21 344 389 Coffee break: Concurrent thinking
- 22 345 390 Coffee break explained: Concurrent thinking
- 23 346 391 The need for asynchronicity
- 24 347 392 Preparing for asynchronous access
- 25 348 393 Designing functional asynchronous programs
- 26 349 394 Managing fibers manually
- 27 350 395 Coding functional asynchronous programs
Part 3 Applied Functional Programming..........397
11 Designing functional programs 399
- 01 351 400 Make it work, make it right, make it fast
- 02 352 401 Modeling using immutable values
- 03 353 402 Business domain modeling and FP
- 04 354 403 Data access modeling
- 05 355 404 A bag of functions
- 06 356 405 Business logic as a pure function
- 07 357 406 Separating the real data access concern
- 08 358 407 Integrating with APIs using imperative libraries and IO
- 09 359 410 Following the design
- 10 360 411 Implementing input actions as IO values
- 11 361 413 Separating the library IO from other concerns
- 12 362 414 Currying and inversion of control
- 13 363 415 Functions as values
- 14 364 416 Connecting the dots
- 15 365 417 We made it work
- 16 366 418 Making it right
- 17 367 419 Resource leaks
- 18 368 420 Handling resources
- 19 369 421 Using a Resource value
- 20 370 422 We made it right
- 21 371 423 Coffee break: Make it fast
- 22 372 424 Coffee break explained: Make it fast
12 Testing Functional Programs 427
- 01 373 428 Do you have tests for that?
- 02 374 429 Tests are just functions
- 03 375 430 Choosing functions to test
- 04 376 431 Testing by providing examples
- 05 377 432 Practicing testing by example
- 06 378 433 Generating good examples
- 07 379 434 Generating properties
- 08 380 435 Property-based testing
- 09 381 436 Testing by providing properties
- 10 382 437 Delegating the work by passing functions
- 11 383 438 Understanding failures of property-based tests
- 12 384 439 Wrong test or a bug?
- 13 385 440 Custom generators
- 14 386 441 Using custom generators
- 15 387 442 Testing more complicated scenarios in a readable way
- 16 388 443 Finding and fixing bugs in the implementation
- 17 389 444 Coffee break: Property-based tests
- 18 390 445 Coffee break explained: Property-based tests
- 19 391 446 Properties and examples
- 20 392 447 Requirements coverage
- 21 393 448 Testing side-effectful requirements
- 22 394 449 Identifying the right test for the job
- 23 395 450 Data usage tests
- 24 396 452 Practicing stubbing external services using IO
- 25 397 453 Testing and design
- 26 398 454 Service integration tests
- 27 399 455 Local servers as Resources in integration tests
- 28 400 456 Writing isolated integration tests
- 29 401 457 Integration with a service is a single responsibility
- 30 402 458 Coffee break: Writing integration tests
- 31 403 459 Coffee break explained: Writing integration tests
- 32 404 460 Integration tests take more time
- 33 405 461 Property-based integration tests
- 34 406 462 Choosing the right testing approach
- 35 407 463 Test-driven development
- 36 408 464 Writing a test for a feature that doesn’t exist
- 37 409 465 Red-green-refactor
- 38 410 466 Making tests green
- 39 411 467 Adding more red tests
- 40 412 468 The last TDD iteration