Table Of ContentPierre-Yves Saumont
M A N N I N G
The Joy of Kotlin
PIERRE-YVES SAUMONT
MANNING
Shelter ISland
For online information and ordering of this and other Manning books, please visit www.manning.com.
The publisher offers discounts on this book when ordered in quantity.
For more information, please contact
Special Sales Department
Manning Publications Co.
20 Baldwin Road
PO Box 761
Shelter Island, NY 11964
Email: orders@manning.com
©2019 by Manning Publications Co. All rights reserved.
No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form
or by means electronic, mechanical, photocopying, or otherwise, without prior written permission of the
publisher.
Many of the designations used by manufacturers and sellers to distinguish their products are claimed
as trademarks. Where those designations appear in the book, and Manning Publications was aware
of a trademark claim, the designations have been printed in initial caps or all caps.
∞Recognizing the importance of preserving what has been written, it is Manning’s policy to have the books
we publish printed on acid- free paper, and we exert our best efforts to that end. Recognizing also our
responsibility to conserve the resources of our planet, Manning books are printed on paper that is at
least 15 percent recycled and processed without the use of elemental chlorine.
Manning Publications Co. Development editor: Marina Michaels and
20 Baldwin Road Kristen Waterson
PO Box 761 Technical development editor: Riccardo Terrell, Joshua White,
Shelter Island, NY 11964 and Joel Kotarski
Review editor: Aleks Dragosavljevic´
Production editor: Deirdre Hiam
Copy editor: Frances Buran
Proofreader: Keri Hales
Technical proofreader: Alessandro Campeis
Typesetter: Happenstance Type-O-Rama
Cover designer: Marija Tudor
ISBN 9781617295362
Printed in the United States of America
1 2 3 4 5 6 7 8 9 10 – SP – 24 23 22 21 20 19
contents
preface xi
acknowledgments xiv
about this book xv
about the author xxi
about the cover illustration xxii
1 Making programs safer 1
1.1 Programming traps 3
Safely handling effects 5 ■ Making programs safer with referential
transparency 6
1.2 The benefits of safe programming 6
Using the substitution model to reason about
programs 8 ■ Applying safe principles to a simple
example 9 ■ Pushing abstraction to the limit 13
2 Functional programming in Kotlin: An overview 15
2.1 Fields and variables in Kotlin 16
Omitting the type to simplify 16 ■ Usingmutable
fields 16 ■ Understandinglazy initialization 17
2.2 Classesand interfaces in Kotlin 18
Making the code even more concise 19 ■ Implementing
aninterface or extending a class 20 ■ Instantiatinga
class 20 ■ Overloadingproperty constructors 20
iii
iivv contents
Creating equals and hashCode methods 22 ■ Destructuring data
objects 23 ■ Implementing static members in Kotlin 23 ■ Using
singletons 24 ■ Preventing utility class instantiation 24
2.3 Kotlin doesn’t have primitives 25
2.4 Kotlin’s two types of collections 25
2.5 Kotlin’s packages 27
2.6 Visibility in Kotlin 27
2.7 Functions in Kotlin 28
Declaring functions 28 ■ Using local functions 29
Overriding functions 30 ■ Using extension
functions 30 ■ Using lambdas 31
2.8 Nulls in Kotlin 32
Dealing with nullable types 33 ■ Elvis and the default value 34
2.9 Program flow and control structures 34
Using conditional selectors 34 ■ Using multi-conditional
selectors 35 ■ Using loops 36
2.10 Kotlin’s unchecked exceptions 37
2.11 Automatic resource closure 38
2.12 Kotlin’s smart casts 39
2.13 Equality versus identity 40
2.14 String interpolation 40
2.15 Multi-line strings 41
2.16 Variance: parameterized types and subtyping 41
Why is variance a potential problem? 41 ■ When to use covariance
and when to use contravariance 42 ■ Declaration-site variance
versus use-site variance 43
3 Programming with functions 46
3.1 What’s a function? 47
Understanding the relationship between two function sets 47
An overview of inverse functions in Kotlin 49
Working with partial functions 49 ■ Understanding function
composition 50 ■ Using functions of several arguments 50
Currying functions 51 ■ Using partially-applied
functions 51 ■ Functions have no effects 52
contents vv
3.2 Functions in Kotlin 52
Understanding functions as data 53 ■ Understanding
data as functions 53 ■ Using object constructors as
functions 53 ■ Using Kotlin’s fun functions 54 ■ Using
object notation versus functional notation 57 ■ Using value
functions 58 ■ Using function references 59 ■ Composing
functions 60 ■ Reusing functions 61
3.3 Advanced function features 62
What about functions of several arguments? 62 ■ Applying
curried functions 63 ■ Implementing higher-order
functions 63 ■ Creating polymorphic HOFs 64 ■ Using
anonymous functions 66 ■ Defining local functions 68
Implementing closures 68 ■ Applying functions partially and
automatic currying 69 ■ Switching arguments of partially-applied
functions 73 ■ Declaring the identity function 74 ■ Using the
right types 75
4 Recursion, corecursion, and memoization 81
4.1 Corecursion and recursion 82
Implementing corecursion 82 ■ Implementing
recursion 83 ■ Differentiating recursive and corecursive
functions 84 ■ Choosing recursion or corecursion 86
4.2 Tail Call Elimination 88
Using Tail Call Elimination 88 ■ Switching from loops to
corecursion 89 ■ Using recursive value functions 92
4.3 Recursive functions and lists 94
Using doubly recursive functions 96 ■ Abstracting recursion
on lists 99 ■ Reversing a list 102 ■ Building corecursive
lists 103 ■ The danger of strictness 106
4.4 Memoization 107
Using memoization in loop-based programming 107 ■ Using
memoization in recursive functions 108 ■ Using implicit
memoization 109 ■ Using automatic memoization 111
Implementing memoization of multi-argument functions 113
4.5 Are memoized functions pure? 115
5 Data handling with lists 117
5.1 How to classify data collections 118
5.2 Different types of lists 118
vvii contents
5.3 Relative expected list performance 119
Trading time against memory space and
complexity 120 ■ Avoiding in-place mutation 121
5.4 What kinds of lists are available in Kotlin? 122
Using persistent data structures 122 ■ Implementing immutable,
persistent, singly linked lists 123
5.5 Data sharing in list operations 126
5.6 More list operations 128
Benefiting from object notation 129 ■ Concatenating
lists 131 ■ Dropping from the end of a list 133 ■ Using
recursion to fold lists with higher-order functions
(HOFs) 134 ■ Using variance 135 ■ Creating a stack-safe
recursive version of foldRight 144 ■ Mapping and filtering
lists 146
6 Dealing with optional data 149
6.1 Problems with the null pointer 150
6.2 How Kotlin handles null references 152
6.3 Alternatives to null references 153
6.4 Using the Option type 156
Getting a value from an Option 158 ■ Applying
functions to optional values 159 ■ Dealing with Option
composition 160 ■ Option use cases 161 ■ Other
ways to combine options 165 ■ Composing List with
Option 167 ■ Using Option and when to do so 169
7 Handling errors and exceptions 171
7.1 The problems with missing data 172
7.2 The Either type 173
7.3 The Result type 176
7.4 Result patterns 178
7.5 Advanced Result handling 184
Applying predicates 184
7.6 Mapping failures 186
7.7 Adding factory functions 186
7.8 Applying effects 187
7.9 Advanced result composition 190
contents vviiii
8 Advanced list handling 195
8.1 The problem with length 196
8.2 The performance problem 196
8.3 The benefits of memoization 197
Handling memoization drawbacks 197 ■ Evaluating performance
improvements 199
8.4 List and Result composition 199
Handling lists returning Result 199 ■ Converting from
List<Result> to Result<List> 201
8.5 Common List abstractions 203
Zipping and unzipping lists 204 ■ Accessing elements by
their index 206 ■ Splitting lists 211 ■ Searching for
sublists 214 ■ Miscellaneous functions for working with
lists 215
8.6 Automatic parallel processing of lists 220
Not all computations can be parallelized 220 ■ Breaking the list
into sublists 220 ■ Processing sublists in parallel 222
9 Working with laziness 225
9.1 Strictness versus laziness 226
9.2 Kotlin and strictness 227
9.3 Kotlin and laziness 229
9.4 Laziness implementations 230
Composing lazy values 232 ■ Lifting functions 235 ■ Mapping
and flatMapping Lazy 237 ■ Composing Lazy with
List 239 ■ Dealing with exceptions 240
9.5 Further lazy compositions 242
Lazily applying effects 242 ■ Things you can’t do without
laziness 244 ■ Creating a lazy list data structure 245
9.6 Handling streams 247
Folding streams 253 ■ Tracing evaluation and function
application 256 ■ Applying streams to concrete problems 257
10 More data handling with trees 261
10.1 The binary tree 262
vviiiiii contents
10.2 Understanding balanced and unbalanced trees 263
10.3 Looking at size, height, and depth in trees 263
10.4 Empty trees and the recursive definition 264
10.5 Leafy trees 264
10.6 Ordered binary trees or binary search trees 265
10.7 Insertion order and the structure of trees 266
10.8 Recursive and non-recursive tree traversal order 267
Traversing recursive trees 267 ■ Non-recursive traversal
orders 269
10.9 Binary search tree implementation 269
Understanding variance and trees 270 ■ What about an
abstract function in the Tree class? 272 ■ Overloading
operators 272 ■ Recursion in trees 273 ■ Removing elements
from trees 276 ■ Merging arbitrary trees 278
10.10 About folding trees 283
Folding with two functions 284 ■ Folding with a single
function 286 ■ Choosing a fold implementation 287
10.11 About mapping trees 289
10.12 About balancing trees 290
Rotating trees 290 ■ Using the Day-Stout-Warren
algorithm 292 ■ Automatically balancing trees 296
11 Solving problems with advanced trees 298
11.1 Better performance and stack safety
with self-balancing trees 299
Understanding the basic red-black tree structure 299 ■ Adding
an element to the red-black tree 301 ■ Removing elements from the
red-black tree 307
1 1.2 A use case for the red-black tree: Maps 307
Implementing Map 307 ■ Extending maps 309 ■ Using Map
with noncomparable keys 311
1 1.3 Implementing a functional priority queue 313
Looking at the priority queue access protocol 313 ■ Exploring
priority queue use cases 313 ■ Looking at
implementation requirements 314 ■ The leftist
heap data structure 314 ■ Implementing the leftist
heap 315 ■ Implementing the queue-like interface 318