The Joy of Kotlin
Pierre-Yves Saumont
  • MEAP began January 2018
  • Publication in December 2018 (estimated)
  • ISBN 9781617295362
  • 475 pages (estimated)
  • printed in black & white
Drudgery like 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, design and write comprehensible code, and build maintainable bug-free applications.
Table of Contents detailed table of contents

1 Making programs safer

1.1 Identifying programming traps

1.2 Safely handling effects

1.3 How referential transparency makes programs safer

1.4 The benefits of "safe" programming

1.5 Using the substitution model to reason about programs

1.6 Applying "safe" principles to a simple example

1.7 Pushing abstraction to the limit

1.8 Summary

2 An Overview of Kotlin

2.1 Declaring and initializing fields and variables

2.1.1 Omitting the type to simplify

2.1.2 Mutable fields

2.1.3 Lazy initialization

2.1.4 Delayed 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 constructors

2.2.5 Private constructors

2.2.6 Using accessors

2.2.7 Creating equals and hashCode

2.2.8 Destructuring data objects

2.2.9 Getting the effect of static members in Kotlin

2.2.10 Using singletons

2.2.11 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.6.1 Visibility of classes, interfaces, and class constructors

2.7 Using functions in Kotlin

2.7.1 Declaring functions

2.7.2 Local functions

2.7.3 Overriding functions

2.7.4 Using extension functions

2.7.5 Using lambdas

2.7.6 Specifying lambda parameter types

2.7.7 Lambdas with multi-line implementation

2.7.8 Using the simplified it syntax for lambdas

2.7.9 Lambdas and closures

2.8 Handling nulls in Kotlin

2.8.1 Operators for dealing with nullable types

2.8.2 Elvis and the default value

2.9 Controlling program flow with control structures

2.9.1 Using conditional selectors

2.9.2 Using multi-conditional selectors

2.9.3 Kotlin’s loops

2.10 All Kotlin exceptions are unchecked

2.10.1 Automatically closing resources with use

2.11 Using Kotlin’s smart casts

2.12 Equality versus identity

2.13 String interpolation

2.13.1 Formatting multi-line strings

2.14 Variance: parameterized types and subtyping

2.14.1 Why is variance a potential problem?

2.14.2 When to use covariance and when to use contravariance

2.14.3 Declaration-site variance versus use-site variance

2.15 Summary

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 Functions as data

3.2.2 Data as functions

3.2.3 Object constructors as functions

3.3 Using fun functions

3.4 Value functions

3.5 Using function references

3.6 Composing functions

3.7 Polymorphic functions

3.8 Advanced function features

3.9 What about functions of several arguments?

3.10 Applying curried functions

3.11 Higher-order functions

3.12 Polymorphic higher-order functions

3.13 Using anonymous functions

3.13.1 When to use anonymous functions and when to use named functions

3.13.2 Type inference

3.14 Local functions

3.15 Closures

3.16 Partial function application and automatic currying

3.16.1 Switching arguments of partially applied functions

3.17 The identity function

3.18 Using the right types

3.18.1 Problems with standard types

3.18.2 Defining value types

3.19 Summary

4 Recursion, corecursion, and memoization

4.1 Understanding corecursion and recursion

4.1.1 Implementing corecursion

4.1.2 Implementing recursion

4.2 How to differentiate recursive and corecursive functions

4.3 Should you choose recursion or corecursion?

4.4 Tail Call Elimination

4.4.1 Using Tail Call Elimination

4.5 Creating recursive value functions

4.6 Using recursive functions on lists

4.7 Doubly recursive functions

4.8 Abstracting recursion on lists

4.9 Reversing a list

4.10 Building corecursive lists

4.11 The danger of strictness

4.12 Using memoization

4.12.1 Memoization in loop based programming

4.12.2 Memoization in recursive functions

4.12.3 Implicit memoization

4.12.4 Automatic memoization

4.12.5 Memoization of "multi-argument" functions

4.12.6 Are memoized functions pure?

4.13 Summary

5 Data handling with lists

5.1 How to classify data collections

5.1.1 Different types of lists

5.1.2 Relative expected list performance

5.1.3 Trading time against memory space, and time against complexity

5.1.4 Why in-place mutation should be avoided

5.2 What kinds of lists are available in Kotlin?

5.3 Persistent data structures

5.4 An immutable, persistent, singly linked list implementation

5.5 Data sharing in list operations

5.6 More list operations

5.7 Benefits of object notation

5.8 Concatenating lists

5.9 Dropping from the end of a list

5.10 Using recursion to fold lists with higher-order functions

5.11 Using Variance

5.11.1 Understanding variance

5.11.2 How to get away with variance abuse

5.12 Stack safe recursive version of foldRight

5.13 Mapping and filtering lists

5.14 Summary

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 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.5 How and when to use Option

6.6 Summary

7 Handling errors and exceptions

7.1 The problems to be solved

7.2 The Either type

7.2.1 Composing Either

7.3 The Result type

7.3.1 Adding functions to the Result class

7.4 Result patterns

7.5 Advanced Result handling

7.5.1 Applying predicates

7.5.2 Mapping failures

7.5.3 Adding factory functions

7.5.4 Applying effects

7.5.5 Advanced result composition

7.6 Summary

8 Advanced list handling

8.1 The problem with length

8.2 The performance problem

8.3 The benefits of memoization

8.4 The drawbacks of memoization

8.5 Evaluating actual performance improvement

8.6 Composing List and Result

8.7 Functions on List returning Result

8.8 Converting from List<Result> to Result<List>

8.9 Abstracting common List use cases

8.9.1 Zipping and unzipping lists

8.9.2 Accessing elements by their index

8.9.3 Splitting lists

8.9.4 Searching for sublists

8.9.5 Miscellaneous functions for working with lists

8.10 Automatic parallel processing of lists

8.10.1 Not all computations can be parallelized

8.10.2 Breaking the list into sublists

8.10.3 Processing sublists in parallel

8.11 Summary

9 Working with laziness

9.1 Understanding strictness and laziness

9.1.1 How strict is Kotlin?

9.1.2 The problem with strictness

9.2 Using laziness in Kotlin

9.3 Implementing laziness

9.4 Composing lazy values

9.4.1 Lifting functions

9.4.2 Mapping and flatMapping Lazy

9.4.3 Composing Lazy with List

9.4.4 Dealing with exceptions

9.4.5 Further lazy composition

9.4.6 Lazily applying effects

9.5 Things you can’t do without laziness

9.6 Creating a lazy list data structure

9.6.1 Handling streams

9.6.2 Folding streams

9.6.3 Tracing evaluation and function application

9.6.4 Applying streams to concrete problems

9.7 Summary

10 More data handling with trees

10.1 The binary tree

10.1.1 Balanced and unbalanced trees

10.1.2 Size, height, and depth in trees

10.1.3 Leafy trees

10.1.4 Ordered binary trees or binary search trees (BST)

10.1.5 Insertion order affects the structure of a tree

10.1.6 Recursive and non-recursive tree traversal order

10.2 Implementing the binary search tree

10.2.1 Variance and trees

10.2.2 What about an abstract function in the Tree class?

10.2.3 Overloading operators

10.2.4 Recursion in trees

10.3 Removing elements from trees

10.4 Merging arbitrary trees

10.5 Folding trees

10.5.1 Folding with two functions

10.5.2 Folding with a single function

10.5.3 Which fold implementation to choose?

10.6 Mapping trees

10.7 Balancing trees

10.7.1 Rotating trees

10.7.2 Balancing trees using the Day-Stout-Warren algorithm

10.7.3 Automatically balancing trees

10.8 Summary

11 Solving real problems with advanced trees

11.1 Better performance and stack safety with self-balancing trees

11.1.1 The basic 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 non comparable keys

11.3 Implementing a functional priority queue

11.3.1 The priority queue access protocol

11.3.2 Priority queue use cases

11.3.3 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 Retrieving elements in sorted order

11.5 A priority queue for non comparable elements

11.6 Summary

12 Functional input/output

12.1 Applying effects in context

12.1.1 What are 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.2.3 Testing with input

12.3 Fully functional input/output

12.3.1 How can input/output be made fully functional?

12.3.2 Implementing purely functional input/output

12.3.3 Combining IO

12.3.4 Handling input with IO

12.3.5 Extending the IO type

12.3.6 Making the IO type stack safe

12.4 Summary

13 Sharing mutable state with actors

13.1 The actor model

13.1.1 Asynchronous messaging

13.1.2 Handling parallelization

13.1.3 Handling actor state mutation

13.2 Building the actor framework

13.2.1 Limitations of this actor framework

13.2.2 Designing the actor framework interfaces

13.2.3 The AbstractActor implementation

13.3 Putting actors to work

13.3.1 Implementing the ping pong example

13.3.2 A more serious example: running a computation in parallel

13.3.3 Reordering the results

13.3.4 Optimizing performance

13.4 Summary

14 Solving common problems functionally

14.1 Using assertions to validate data

14.2 Retrying a function or an effect

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: determine the necessary functions

14.4.3 Composing the functions and applying an effect

14.4.4 Implementing the functions

14.4.5 Step 3 - Making the program even more functional

14.4.6 Step 4 - Fixing the argument type problem

14.4.7 Step 5 - Making the element-processing function a parameter

14.4.8 Step 6 - Handling errors on element names

14.4.9 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 Multi-module projects

A.1.5 Adding dependencies to a multiple module project

A.2 Calling Java from Kotlin

A.2.1 Using Java primitives

A.2.2 Useful Java numerical object types

A.2.3 Failing fast on null values

A.2.4 Kotlin and Java String

A.2.5 Other type conversions

A.2.6 Java varargs

A.2.7 Specifying nullability in Java

A.2.8 Calling getters and setters

A.2.9 Java properties with reserved names in Kotlin

A.2.10 Checked exceptions

A.2.11 SAM interfaces

A.3 Calling Kotlin from Java code

A.3.1 Properties

A.3.2 Fields

A.3.3 Static fields

A.3.4 Calling Kotlin functions as Java methods

A.3.5 Converting types from Kotlin to Java

A.3.6 Function types

A.4 Specific problems with mixed Kotlin/Java projects

A.5 Summary

Appendix B: Property based testing in Kotlin

B.1 Why property base testing

B.2 Adding necessary dependencies for property based unit testing

B.3 Writing property based tests

B.3.1 Creating your own generators

B.3.2 Simplifying through further abstraction

B.4 Summary

About the Technology

The Kotlin programming language offers an expressive syntax, an intuitive type system, and support for a host of tools for you to use. Kotlin runs on the JVM, so it seamlessly integrates with the Java libraries you're already using. Now officially supported by Google as a first-class Android language, there's never been a better time to see what Kotlin can do for you!

About the book

The Joy of Kotlin teaches you the right way to code in Kotlin. In this insight-rich book, you'll master the Kotlin language while exploring coding techniques that will make you a better developer no matter what language you use. Kotlin natively supports a functional style of programming, so seasoned author Pierre-Yves Saumont begins by reviewing the FP principles of immutability, referential transparency, and the separation between functions and effects. Then, you'll move deeper into using Kotlin in the real world, as you learn to handle errors and data properly, encapsulate shared state mutations, and work with laziness. This book will change the way you code—and give you back some of the joy you had when you first started.

What's inside

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

About the reader

Written for intermediate Java or Kotlin developers.

About the author

Pierre-Yves Saumont is an R&D software engineer at Alcatel-Submarine Networks, where he has overseen the previous switch from C to Java, and now to Kotlin too. He has developed several fully functional libraries that are used by business developers. Pierre-Yves is the author of Functional Programming in Java.

Manning Early Access Program (MEAP) Read chapters as they are written, get the finished eBook as soon as it’s ready, and receive the pBook long before it's in bookstores.

FREE domestic shipping on three or more pBooks