Pierre-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: [email protected] ©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