O'Reilly logo

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

The Joy of Kotlin

Book Description

The Joy of Kotlin teaches you to write comprehensible, easy-to-maintain, safe programs with Kotlin. In this expert guide, seasoned engineer Pierre-Yves Saumont teaches you to approach common programming challenges with a fresh, FP-inspired perspective. As you work through the many examples, you’ll dive deep into handling errors and data properly, managing state, and taking advantage of laziness. The author’s down-to-earth examples and experience-driven insights will make you a better—and more joyful—developer!

Table of Contents

  1. Cover
  2. Titlepage
  3. Copyright
  4. contents in brief
  5. contents
  6. preface
  7. acknowledgments
  8. about this book
    1. Who should read this book
      1. What you’ll learn
      2. Pushing abstraction further
      3. Immutability
      4. Referential transparency
      5. Encapsulated state mutation sharing
      6. Abstracting control flow and control structures
      7. Using the right types
      8. Laziness
      9. Audience
    2. How this book is organized: A roadmap
      1. Completing the exercises
      2. Learning the techniques in this book
    3. About the code
    4. liveBook discussion
  9. about the author
  10. about the cover illustration
  11. Chapter 1: Making programs safer
    1. 1.1 Programming traps
      1. 1.1.1 Safely handling effects
      2. 1.1.2 Making programs safer with referential transparency
    2. 1.2 The benefits of safe programming
      1. 1.2.1 Using the substitution model to reason about programs
      2. 1.2.2 Applying safe principles to a simple example
      3. 1.2.3 Pushing abstraction to the limit
    3. Summary
  12. Chapter 2: Functional programming in Kotlin: An overview
    1. 2.1 Fields and variables in Kotlin
      1. 2.1.1 Omitting the type to simplify
      2. 2.1.2 Using mutable fields
      3. 2.1.3 Understanding lazy initialization
    2. 2.2 Classes and interfaces in Kotlin
      1. 2.2.1 Making the code even more concise
      2. 2.2.2 Implementing an interface or extending a class
      3. 2.2.3 Instantiating a class
      4. 2.2.4 Overloading property constructors
      5. 2.2.5 Creating equals and hashCode methods
      6. 2.2.6 Destructuring data objects
      7. 2.2.7 Implementing static members in Kotlin
      8. 2.2.8 Using singletons
      9. 2.2.9 Preventing utility class instantiation
    3. 2.3 Kotlin doesn’t have primitives
    4. 2.4 Kotlin’s two types of collections
    5. 2.5 Kotlin’s packages
    6. 2.6 Visibility in Kotlin
    7. 2.7 Functions in Kotlin
      1. 2.7.1 Declaring functions
      2. 2.7.2 Using local functions
      3. 2.7.3 Overriding functions
      4. 2.7.4 Using extension functions
      5. 2.7.5 Using lambdas
    8. 2.8 Nulls in Kotlin
      1. 2.8.1 Dealing with nullable types
      2. 2.8.2 Elvis and the default value
    9. 2.9 Program flow and control structures
      1. 2.9.1 Using conditional selectors
      2. 2.9.2 Using multi-conditional selectors
      3. 2.9.3 Using loops
    10. Kotlin’s unchecked exceptions
    11. Automatic resource closure
    12. Kotlin’s smart casts
    13. Equality versus identity
    14. String interpolation
    15. Multi-line strings
    16. Variance: parameterized types and subtyping
      1. 2.16.1 Why is variance a potential problem?
      2. 2.16.2 When to use covariance and when to use contravariance
      3. 2.16.3 Declaration-site variance versus use-site variance
    17. Summary
  13. Chapter 3: Programming with functions
    1. 3.1 What’s a function?
      1. 3.1.1 Understanding the relationship between two function sets
      2. 3.1.2 An overview of inverse functions in Kotlin
      3. 3.1.3 Working with partial functions
      4. 3.1.4 Understanding function composition
      5. 3.1.5 Using functions of several arguments
      6. 3.1.6 Currying functions
      7. 3.1.7 Using partially-applied functions
      8. 3.1.8 Functions have no effects
    2. 3.2 Functions in Kotlin
      1. 3.2.1 Understanding functions as data
      2. 3.2.2 Understanding data as functions
      3. 3.2.3 Using object constructors as functions
      4. 3.2.4 Using Kotlin’s fun functions
      5. 3.2.5 Using object notation versus functional notation
      6. 3.2.6 Using value functions
      7. 3.2.7 Using function references
      8. 3.2.8 Composing functions
      9. 3.2.9 Reusing functions
    3. 3.3 Advanced function features
      1. 3.3.1 What about functions of several arguments?
      2. 3.3.2 Applying curried functions
      3. 3.3.3 Implementing higher-order functions
      4. 3.3.4 Creating polymorphic HOFs
      5. 3.3.5 Using anonymous functions
      6. 3.3.6 Defining local functions
      7. 3.3.7 Implementing closures
      8. 3.3.8 Applying functions partially and automatic currying
      9. 3.3.9 Switching arguments of partially-applied functions
      10. Declaring the identity function
      11. Using the right types
    4. Summary
  14. Chapter 4: Recursion, corecursion, and memoization
    1. 4.1 Corecursion and recursion
      1. 4.1.1 Implementing corecursion
      2. 4.1.2 Implementing recursion
      3. 4.1.3 Differentiating recursive and corecursive functions
      4. 4.1.4 Choosing recursion or corecursion
    2. 4.2 Tail Call Elimination
      1. 4.2.1 Using Tail Call Elimination
      2. 4.2.2 Switching from loops to corecursion
      3. 4.2.3 Using recursive value functions
    3. 4.3 Recursive functions and lists
      1. 4.3.1 Using doubly recursive functions
      2. 4.3.2 Abstracting recursion on lists
      3. 4.3.3 Reversing a list
      4. 4.3.4 Building corecursive lists
      5. 4.3.5 The danger of strictness
    4. 4.4 Memoization
      1. 4.4.1 Using memoization in loop-based programming
      2. 4.4.2 Using memoization in recursive functions
      3. 4.4.3 Using implicit memoization
      4. 4.4.4 Using automatic memoization
      5. 4.4.5 Implementing memoization of multi-argument functions
    5. 4.5 Are memoized functions pure?
    6. Summary
  15. Chapter 5: Data handling with lists
    1. 5.1 How to classify data collections
    2. 5.2 Different types of lists
    3. 5.3 Relative expected list performance
      1. 5.3.1 Trading time against memory space and complexity
      2. 5.3.2 Avoiding in-place mutation
    4. 5.4 What kinds of lists are available in Kotlin?
      1. 5.4.1 Using persistent data structures
      2. 5.4.2 Implementing immutable, persistent, singly linked lists
    5. 5.5 Data sharing in list operations
    6. 5.6 More list operations
      1. 5.6.1 Benefiting from object notation
      2. 5.6.2 Concatenating lists
      3. 5.6.3 Dropping from the end of a list
      4. 5.6.4 Using recursion to fold lists with higher-order functions (HOFs)
      5. 5.6.5 Using variance
      6. 5.6.6 Creating a stack-safe recursive version of foldRight
      7. 5.6.7 Mapping and filtering lists
    7. Summary
  16. Chapter 6: Dealing with optional data
    1. 6.1 Problems with the null pointer
    2. 6.2 How Kotlin handles null references
    3. 6.3 Alternatives to null references
    4. 6.4 Using the Option type
      1. 6.4.1 Getting a value from an Option
      2. 6.4.2 Applying functions to optional values
      3. 6.4.3 Dealing with Option composition
      4. 6.4.4 Option use cases
      5. 6.4.5 Other ways to combine options
      6. 6.4.6 Composing List with Option
      7. 6.4.7 Using Option and when to do so
    5. Summary
  17. Chapter 7: Handling errors and exceptions
    1. 7.1 The problems with missing data
    2. 7.2 The Either type
    3. 7.3 The Result type
    4. 7.4 Result patterns
    5. 7.5 Advanced Result handling
      1. 7.5.1 Applying predicates
    6. 7.6 Mapping failures
    7. 7.7 Adding factory functions
    8. 7.8 Applying effects
    9. 7.9 Advanced result composition
    10. Summary
  18. Chapter 8: Advanced list handling
    1. 8.1 The problem with length
    2. 8.2 The performance problem
    3. 8.3 The benefits of memoization
      1. 8.3.1 Handling memoization drawbacks
      2. 8.3.2 Evaluating performance improvements
    4. 8.4 List and Result composition
      1. 8.4.1 Handling lists returning Result
      2. 8.4.2 Converting from List<Result> to Result<List>
    5. 8.5 Common List abstractions
      1. 8.5.1 Zipping and unzipping lists
      2. 8.5.2 Accessing elements by their index
      3. 8.5.3 Splitting lists
      4. 8.5.4 Searching for sublists
      5. 8.5.5 Miscellaneous functions for working with lists
    6. 8.6 Automatic parallel processing of lists
      1. 8.6.1 Not all computations can be parallelized
      2. 8.6.2 Breaking the list into sublists
      3. 8.6.3 Processing sublists in parallel
    7. Summary
  19. Chapter 9: Working with laziness
    1. 9.7 Strictness versus laziness
    2. 9.8 Kotlin and strictness
    3. 9.9 Kotlin and laziness
    4. Laziness implementations
      1. 9.10.1 Composing lazy values
      2. 9.10.2 Lifting functions
      3. 9.10.3 Mapping and flatMapping Lazy
      4. 9.10.4 Composing Lazy with List
      5. 9.10.5 Dealing with exceptions
    5. Further lazy compositions
      1. 9.11.1 Lazily applying effects
      2. 9.11.2 Things you can’t do without laziness
      3. 9.11.3 Creating a lazy list data structure
    6. Handling streams
      1. 9.12.1 Folding streams
      2. 9.12.2 Tracing evaluation and function application
      3. 9.12.3 Applying streams to concrete problems
    7. Summary
  20. Chapter 10: More data handling with trees
    1. 10.1 The binary tree
    2. 10.2 Understanding balanced and unbalanced trees
    3. 10.3 Looking at size, height, and depth in trees
    4. 10.4 Empty trees and the recursive definition
    5. 10.5 Leafy trees
    6. 10.6 Ordered binary trees or binary search trees
    7. 10.7 Insertion order and the structure of trees
    8. 10.8 Recursive and non-recursive tree traversal order
      1. 10.8.1 Traversing recursive trees
      2. 10.8.2 Non-recursive traversal orders
    9. 10.9 Binary search tree implementation
      1. 10.9.1 Understanding variance and trees
      2. 10.9.2 What about an abstract function in the Tree class?
      3. 10.9.3 Overloading operators
      4. 10.9.4 Recursion in trees
      5. 10.9.5 Removing elements from trees
      6. 10.9.6 Merging arbitrary trees
    10. About folding trees
      1. 10.10.1 Folding with two functions
      2. 10.10.2 Folding with a single function
      3. 10.10.3 Choosing a fold implementation
    11. About mapping trees
    12. About balancing trees
      1. 10.12.1 Rotating trees
      2. 10.12.2 Using the Day-Stout-Warren algorithm
      3. 10.12.3 Automatically balancing trees
    13. Summary
  21. Chapter 11: Solving problems with advanced trees
    1. 11.1 Better performance and stack safety with self-balancing trees
      1. 11.1.1 Understanding the basic red-black tree structure
      2. 11.1.2 Adding an element to the red-black tree
      3. 11.1.3 Removing elements from the red-black tree
    2. 11.2 A use case for the red-black tree: Maps
      1. 11.2.1 Implementing Map
      2. 11.2.2 Extending maps
      3. 11.2.3 Using Map with noncomparable keys
    3. 11.3 Implementing a functional priority queue
      1. 11.3.1 Looking at the priority queue access protocol
      2. 11.3.2 Exploring priority queue use cases
      3. 11.3.3 Looking at implementation requirements
      4. 11.3.4 The leftist heap data structure
      5. 11.3.5 Implementing the leftist heap
      6. 11.3.6 Implementing the queue-like interface
    4. 11.4 Elements and sorted lists
    5. 11.5 A priority queue for noncomparable elements
    6. Summary
  22. Chapter 12: Functional input/output
    1. 12.1 What does effects in context mean?
      1. 12.1.1 Handling effects
      2. 12.1.2 Implementing effects
    2. 12.2 Reading data
      1. 12.2.1 Reading data from the console
      2. 12.2.2 Reading from a file
    3. 12.3 Testing with input
    4. 12.4 Fully functional input/output
      1. 12.4.1 Making input/output fully functional
      2. 12.4.2 Implementing purely functional I/O
      3. 12.4.3 Combining I/O
      4. 12.4.4 Handling input with IO
      5. 12.4.5 Extending the IO type
      6. 12.4.6 Making the IO type stack-safe
    5. Summary
  23. Chapter 13: Sharing mutable states with actors
    1. 13.1 The actor model
      1. 13.1.1 Understanding asynchronous messaging
      2. 13.1.2 Handling parallelization
      3. 13.1.3 Handling actor state mutation
    2. 13.2 An actor framework implementation
      1. 13.2.1 Understanding the limitations
      2. 13.2.2 Designing the actor framework interfaces
    3. 13.3 The AbstractActor implementation
    4. 13.4 Putting actors to work
      1. 13.4.1 Implementing the Ping Pong example
      2. 13.4.2 Running a computation in parallel
      3. 13.4.3 Reordering the results
      4. 13.4.4 Optimizing performance
    5. Summary
  24. Chapter 14: Solving common problems functionally
    1. 14.1 Assertions and data validation
    2. 14.2 Retries for functions and effects
    3. 14.3 Reading properties from a file
      1. 14.3.1 Loading the property file
      2. 14.3.2 Reading properties as strings
      3. 14.3.3 Producing better error messages
      4. 14.3.4 Reading properties as lists
      5. 14.3.5 Reading enum values
      6. 14.3.6 Reading properties of arbitrary types
    4. 14.4 Converting an imperative program: The XML reader
      1. 14.4.1 Step 1: The imperative solution
      2. 14.4.2 Step 2: Making an imperative program more functional
      3. 14.4.3 Step 3: Making the program even more functional
      4. 14.4.4 Step 4: Fixing the argument type problem
      5. 14.4.5 Step 5: Making the element-processing function a parameter
      6. 14.4.6 Step 6: Handling errors on element names
      7. 14.4.7 Step 7: Additional improvements to the formerly imperative code
    5. Summary
  25. appendix A: Mixing Kotlin with Java
    1. Creating and managing mixed projects
      1. Creating a simple project with Gradle
      2. Importing your Gradle project into IntelliJ
      3. Adding dependencies to your project
      4. Creating multi-module projects
      5. Adding dependencies to a multiple module project
    2. Java library methods and Kotlin code
      1. Using Java primitives
      2. Using Java numerical object types
      3. Failing fast on null values
      4. Using Kotlin and Java string types
      5. Implementing other type conversions
      6. Using Java varargs
      7. Specifying nullability in Java
      8. Calling getters and setters
      9. Accessing Java properties with reserved names
      10. Calling checked exceptions
    3. SAM interfaces
    4. Kotlin functions and Java code
      1. Converting Kotlin properties
      2. Using Kotlin public fields
      3. Static fields
      4. Calling Kotlin functions as Java methods
      5. Converting types from Kotlin to Java
      6. Function types
    5. Specific problems with mixed Kotlin/Java projects
    6. Summary
  26. appendix B: Property-based testing in Kotlin
    1. Why property-based testing?
      1. Writing the interface
      2. Writing the tests
    2. What is property-based testing?
    3. Abstraction and property-based tests
    4. Dependencies for property-based unit testing
    5. Writing property based tests
      1. Creating your own custom generators
      2. Using custom generators
      3. Simplifying the code through further abstraction
    6. Summary
  27. Index
  28. List of Figures
  29. List of Tables
  30. List of Listings