The Joy of Kotlin
Pierre-Yves Saumont
  • April 2019
  • ISBN 9781617295362
  • 480 pages
  • printed in black & white
ePub + Kindle available May 8, 2019

A fabulous introduction to the universe of functional programming!

Aleksei Slaikovskii, Oracle

Maintaining poor legacy code, interpreting cryptic comments, and writing the same boilerplate over and over can suck the joy out of your life as a Java developer. Fear not! There's hope! Kotlin is an elegant JVM language with modern features and easy integration with Java. The Joy of Kotlin teaches you practical techniques to improve abstraction and design, to write comprehensible code, and to build maintainable bug-free applications.

Table of Contents detailed table of contents

1 Making programs safer

1.1 Programming traps

1.1.1 Safely handling effects

1.1.2 Making programs safer with referential transparency

1.2 The benefits of safe programming

1.2.1 Using the substitution model to reason about programs

1.2.2 Applying safe principles to a simple example

1.2.3 Pushing abstraction to the limit


2 Functional programming in Kotlin: An overview

2.1 Fields and variables in Kotlin

2.1.1 Omitting the type to simplify

2.1.2 Using mutable fields

2.1.3 Understanding lazy initialization

2.2 Classes and interfaces in Kotlin

2.2.1 Making the code even more concise

2.2.2 Implementing an interface or extending a class

2.2.3 Instantiating a class

2.2.4 Overloading property constructors

2.2.5 Creating equals and hashCode methods

2.2.6 Destructuring data objects

2.2.7 Implementing static members in Kotlin

2.2.8 Using singletons

2.2.9 Preventing utility class instantiation

2.3 Kotlin doesn’t have primitives

2.4 Kotlin’s two types of collections

2.5 Kotlin packages

2.6 Visibility in Kotlin

2.7 Functions in Kotlin

2.7.1 Declaring functions

2.7.2 Using local functions

2.7.3 Overriding functions

2.7.4 Using extension functions

2.7.5 Using lambdas

2.8 Nulls in Kotlin

2.8.1 Dealing with nullable types

2.8.2 Elvis and the default value

2.9 Program flow and control structures

2.9.1 Using conditional selectors

2.9.2 Using multi-conditional selectors

2.9.3 Using loops

2.10 Kotlin’s unchecked exceptions

2.11 Automatic resource closure

2.12 Kotlin’s smart casts

2.13 Equality versus identity

2.14 String interpolation

2.15 Multi-line strings

2.16 Variance: parameterized types and subtyping

2.16.1 Why is variance a potential problem?

2.16.2 When to use covariance and when to use contravariance

2.16.3 Declaration-site variance versus use-site variance


3 Programming with functions

3.1 What is a function?

3.1.1 What makes a relation between two sets a function

3.1.2 An overview of inverse functions in Kotlin

3.1.3 Partial functions

3.1.4 Function composition

3.1.5 Functions of several arguments

3.1.6 Function currying

3.1.7 Partially applied functions

3.1.8 Functions have no effects

3.2 Functions in Kotlin

3.2.1 Understanding functions as data

3.2.2 Understanding data as functions

3.2.3 Using object constructors as functions

3.2.4 Using Kotlin’s fun functions

3.2.5 Using object notation versus functional notation

3.2.6 Using value functions

3.2.7 Using function references

3.2.8 Composing functions

3.2.9 Reusing functions

3.3 Advanced function features

3.3.1 What about functions of several arguments?

3.3.2 Applying curried functions

3.3.3 Implementing higher-order functions

3.3.4 Creating polymorphic HOFs

3.3.5 Using anonymous functions

3.3.6 Defining local functions

3.3.7 Implementing closures

3.3.8 Applying functions partially and automatic currying

3.3.9 Switching arguments of partially-applied functions

3.3.10 Declaring the identity function

3.3.11 Using the right types


4 Recursion, corecursion, and memoization

4.1 Corecursion and recursion

4.1.1 Implementing corecursion

4.1.2 Implementing recursion

4.1.3 Differentiating recursive and corecursive functions

4.1.4 Choosing recursion or corecursion

4.2 Tail Call Elimination

4.2.1 Using Tail Call Elimination

4.2.2 Switching from loops to corecursion

4.2.3 Using recursive value functions

4.3 Recursive functions and lists

4.3.1 Using doubly recursive functions

4.3.2 Abstracting recursion on lists

4.3.3 Reversing a list

4.3.4 Building corecursive lists

4.3.5 The danger of strictness

4.4 Using memoization

4.4.1 Using memoization in loop-based programming

4.4.2 Using memoization in recursive functions

4.4.3 Using implicit memoization

4.4.4 Using automatic memoization

4.4.5 Implementing memoization of multi-argument functions

4.5 Are memoized functions pure?


5 Data handling with lists

5.1 How to classify data collections

5.2 Different types of lists

5.3 Relative expected list performance

5.3.1 Trading time against memory space and complexity

5.3.2 Avoiding in-place mutation

5.4 What kinds of lists are available in Kotlin?

5.4.1 Using persistent data structures

5.4.2 Implementing immutable, persistent, singly linked lists

5.5 Data sharing in list operations

5.6 More list operations

5.6.1 Benefiting from object notation

5.6.2 Concatenating lists

5.6.3 Dropping from the end of a list

5.6.4 Using recursion to fold lists with higher-order functions (HOFs)

5.6.5 Using variance

5.6.6 Creating a stack-safe recursive version of foldRight

5.6.7 Mapping and filtering lists


6 Dealing with optional data

6.1 Problems with the null pointer

6.2 How Kotlin handles null references

6.3 Alternatives to null references

6.4 Using the Option type

6.4.1 Getting a value from an Option

6.4.2 Applying functions to optional values

6.4.3 Dealing with Option composition

6.4.4 Option use cases

6.4.5 Other ways to combine options

6.4.6 Composing List with Option

6.4.7 Using Option and when to do so


7 Handling errors and exceptions

7.1 The problems with missing data

7.2 The Either type

7.3 The Result type

7.4 Result patterns

7.5 Advanced Result handling

7.5.1 Applying predicates

7.6 Mapping failures

7.7 Adding factory functions

7.8 Applying effects

7.9 Advanced result composition


8 Advanced list handling

8.1 The problem with length

8.2 The performance problem

8.3 The benefits of memoization

8.3.1 Handling memoization drawbacks

8.3.2 Evaluating performance improvements

8.4 List and Result composition

8.4.1 Handling lists returning Result

8.4.2 Converting from List<Result> to Result<List>

8.5 Common List abstractions

8.5.1 Zipping and unzipping lists

8.5.2 Accessing elements by their index

8.5.3 Splitting lists

8.5.4 Searching for sublists

8.5.5 Miscellaneous functions for working with lists

8.6 Automatic parallel processing of lists

8.6.1 Not all computations can be parallelized

8.6.2 Breaking the list into sublists

8.6.3 Processing sublists in parallel


9 Working with laziness

9.1 Strictness versus laziness

9.2 Kotlin and strictness

9.3 Kotlin and laziness

9.4 Laziness implementations

9.4.1 Composing lazy values

9.4.2 Lifting functions

9.4.3 Mapping and flatMapping Lazy

9.4.4 Composing Lazy with List

9.4.5 Dealing with exceptions

9.5 Further lazy compositions

9.5.1 Lazily applying effects

9.5.2 Things you can’t do without laziness

9.5.3 Creating a lazy list data structure

9.6 Handling streams

9.6.1 Folding streams

9.6.2 Tracing evaluation and function application

9.6.3 Applying streams to concrete problems


10 More data handling with trees

10.1 The binary tree

10.2 Understanding balanced and unbalanced trees

10.3 Looking at size, height, and depth in trees

10.4 Empty trees and the recursive definition

10.5 Leafy trees

10.6 Ordered binary trees or binary search trees

10.7 Insertion order and the structure of trees

10.8 Recursive and non-recursive tree traversal order

10.8.1 Traversing recursive trees

10.8.2 Non-recursive traversal orders

10.9 Binary search tree implementation

10.9.1 Understanding variance and trees

10.9.2 What about an abstract function in the Tree class?

10.9.3 Overloading operators

10.9.4 Recursion in trees

10.9.5 Removing elements from trees

10.9.6 Merging arbitrary trees

10.10 About folding trees

10.10.1 Folding with two functions

10.10.2 Folding with a single function

10.10.3 Choosing a fold implementation

10.11 About mapping trees

10.12 About balancing trees

10.12.1 Rotating trees

10.12.2 Using the Day-Stout-Warren algorithm

10.12.3 Automatically balancing trees


11 Solving real problems with advanced trees

11.1 Better performance and stack safety with self-balancing trees

11.1.1 Understanding the basic red-black tree structure

11.1.2 Adding an element to the red-black tree

11.1.3 Removing elements from the red-black tree

11.2 A use case for the red-black tree: Maps

11.2.1 Implementing Map

11.2.2 Extending maps

11.2.3 Using Map with noncomparable keys

11.3 Implementing a functional priority queue

11.3.1 Looking at the priority queue access protocol

11.3.2 Exploring priority queue use cases

11.3.3 Looking at implementation requirements

11.3.4 The leftist heap data structure

11.3.5 Implementing the leftist heap

11.3.6 Implementing the queue-like interface

11.4 Elements and sorted lists

11.5 A priority queue for noncomparable elements


12 Functional input/output

12.1 What does effects in context mean?

12.1.1 Handling effects

12.1.2 Implementing effects

12.2 Reading data

12.2.1 Reading data from the console

12.2.2 Reading from a file

12.3 Testing with input

12.4 Fully functional input/output

12.4.1 Making input/output fully functional

12.4.2 Implementing purely functional I/O

12.4.3 Combining IO

12.4.4 Handling input with IO

12.4.5 Extending the IO type

12.4.6 Making the IO type stack-safe


13 Sharing mutable states with actors

13.1 The actor model

13.1.1 Understanding asynchronous messaging

13.1.2 Handling parallelization

13.1.3 Handling actor state mutation

13.2 An actor framework implementation

13.2.1 Understanding the limitations

13.2.2 Designing the actor framework interfaces

13.3 The AbstractActor implementation

13.4 Putting actors to work

13.4.1 Implementing the Ping Pong example

13.4.2 Running a computation in parallel

13.4.3 Reordering the results

13.4.4 Optimizing performance


14 Solving common problems functionally

14.1 Assertions and data validation

14.2 Retries for functions and effects

14.3 Reading properties from a file

14.3.1 Loading the property file

14.3.2 Reading properties as strings

14.3.3 Producing better error messages

14.3.4 Reading properties as lists

14.3.5 Reading enum values

14.3.6 Reading properties of arbitrary types

14.4 Converting an imperative program: The XML reader

14.4.1 Step 1: The imperative solution

14.4.2 Step 2: Making an imperative program more functional

14.4.3 Composing the functions and applying an effect

14.4.4 Step 4: Fixing the argument type problem

14.4.5 Step 5: Making the element-processing function a parameter

14.4.6 Step 6: Handling errors on element names

14.4.7 Step 7: Additional improvements to the formerly imperative code

14.5 Summary


Appendix A: Mixing Kotlin with Java

A.1 Creating and managing mixed projects

A.1.1 Creating a simple project with Gradle

A.1.2 Importing your Gradle project into IntelliJ

A.1.3 Adding dependencies

A.1.4 Creating multi-module projects

A.1.5 Adding dependencies to a multiple module project

A.2 Java library methods and Kotlin code

A.2.1 Using Java primitives

A.2.2 Using Java numerical object types

A.2.3 Failing fast on null values

A.2.4 Using Kotlin and Java string types

A.2.5 Implementing other type conversions

A.2.6 Using Java varargs

A.2.7 Specifying nullability in Java

A.2.8 Calling getters and setters

A.2.9 Accessing Java properties with reserved names

A.2.10 Calling checked exceptions

A.3 SAM interfaces

A.4 Calling Kotlin from Java code

A.4.1 Converting Kotlin properties

A.4.2 Using Kotlin public fields

A.4.3 Static fields

A.4.4 Calling Kotlin functions as Java methods

A.4.5 Converting types from Kotlin to Java

A.4.6 Function types

A.5 Specific problems with mixed Kotlin/Java projects


Appendix B: Property based testing in Kotlin

B.1 Why property-base testing

B.1.1 Writing the interface

B.1.2 Writing the tests

B.2 What is property-based testing?

B.3 Abstraction and property-based tests

B.4 Dependencies for property-based unit testing

B.5 Writing property based tests

B.5.1 Creating your own custom generators

B.5.2 Using custom generators

B.5.3 Simplifying the code through further abstraction


About the Technology

Your programming language should be expressive, safe, flexible, and intuitive, and Kotlin checks all the boxes! This elegant JVM language integrates seamlessly with Java, and makes it a breeze to switch between OO and functional styles of programming. It’s also fully supported by Google as a first-class Android language. Master the powerful techniques in this unique book, and you’ll be able to take on new challenges with increased confidence and skill.

About the book

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!

What's inside

  • Programming with functions
  • Dealing with optional data
  • Safe handling of errors and exceptions
  • Handling and sharing state mutation

About the reader

Written for intermediate Java or Kotlin developers.

About the author

Pierre-Yves Saumont is a senior software engineer at Alcatel-Submarine Networks. He’s the author of Functional Programming in Java (Manning, 2017).

combo $49.99 pBook + eBook + liveBook
eBook $39.99 pdf + ePub + kindle + liveBook

placing your order...

Don't refresh or navigate away from the page.

FREE domestic shipping on three or more pBooks