Skip to content

Instantly share code, notes, and snippets.

@packrat386
Created March 20, 2023 00:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save packrat386/cb34ba3931b02720f25f5ca938dc0811 to your computer and use it in GitHub Desktop.
Save packrat386/cb34ba3931b02720f25f5ca938dc0811 to your computer and use it in GitHub Desktop.
My extended notes on Atomic Kotlin

Atomic Kotlin

Picked up a book cause that worked for C++ and Ruby

I: Programming Basics

Introduction

It wants me to skip the first section if I'm experienced but I kinda don't want to?

No references to other programming languages is interesting for a language that is inherently based on java

Atomic concepts seems good, but they present it as if it's a novel idea which is funny

4 spaces is standard indentation?

Excercises require IntilliJ IDEA, which I don't really intend to use

Why Kotlin?

Early languages focused on hardware limitations. As computers become more powerful, newer languages shift toward more sophisticated programming with an emphasis on reliability.

An interesting theory. I wonder if the power of computers is really what did it though, or just the power of having built up a bedrock of abstractions

Kotlin is compiled, JVM is one option (I think js is another?)

Statically-typed languages like Kotlin discover as many errors as possible at compile time, while dynamic languages perform their safety checks at runtime (some dynamic languages don’t perform as many safety checks as they might).

snarky

I'm intrigued that they list FORTRAN as an inspiration for kotlin, I'm not sure what the influence was other than having been one of the first "high level" programming languages

For example, the GNU Emacs editor is written entirely in LISP, and can be extended using LISP.

parts of emacs are definitely written in C, but w.e

Similar intrigue at listing ALGOL, COBOL, and BASIC

Never really knew Simula was the first "OO" language

I'm not sure what the lesson learned from Pascal was for Kotlin, it notes that constraints at the time made the language awkward but that the people who worked on it went on to write a bunch of other languages

C having such a small blurb is funny to me

Smalltalk is the kind of language I would love to inspect one day. The OO programmers of the world wax lyrical about its ideas but the language itself is long dead.

Bjarne Stroustrup created C++ because he wanted a better C

This has made a lot of people very angry and been widely regarded as a bad move

Not surprise that Java gets a whole page

Although Java was remarkably successful, an important Kotlin design goal is to fix Java’s flaws so programmers can be more productive

interesting

Java being a demonstration of the practicality of virtual machines are GC is funnily enough still up for debate almost 30 years later

At this writing, the Kotlin team is working to add WASM as a target.

I think they done did it https://blog.jetbrains.com/kotlin/2021/11/k2-compiler-kotlin-wasm-and-tooling-announcements-at-the-2021-kotlin-event/

Kotlin notes that it took a lot of ideas from C# in terms of "fixing" Java

Scala was probably one of the first in the family of JVM languages

Everyone says their language prioritizes readability, but nobody seems to define what that truly means

Multi paradigm at it's core is something I haven't fucked with since C++

The two features they want to highlight the most are that it runs on the JVM and thus works with Java and how is handles empty values. An interesting choice for sure, definitely can see what drove the language design (and I wonder how I would feel about it if I were a kotlin -> js user).

Kotlin prevents dereferencing null at compile time (or so it says?)

Hello World!

We can have "just" a top level main like in Go, which is interesting.

We distinguish between statements, which produce an effect, and expressions, which yield a value

An empty main function is a valid program, no need to return or anything

C++ style comments

We don't need to import anything to println

var & val

This is an interesting way to deal with what C++ would call const. Essentially two entirely different ways to declare.

println only takes a single value (do we have variadic functions?)

Kotlin style says first letter of a local is lower, then camelcase, e.g. coffeeTime

Data Types

Kotlin uses the upper case String, Boolean, etc for basic types (like the Java impls?)

Single quote is for Character, not strings

Kotlin infers types when you declare, but you can be explicit

Functions

Kotlin uses name -> type in function declarations (Java does not right?)

Unit is what is returned when there is no return value (kind like void?). You don't have to specify it.

There is an abbreviated syntax for a function that consists of only a single expression (is that gonna be common?)

Kotlin can infer return types for single expression functions, but not block style ones (why?)

if Expression

We use if { expr } else if { expr } else { expr } style

if is an expr itself, we can use it as a value

String Templates

We can interpolate using $ pretty much like in bash. I have no idea why new languages (2011) continue to choose this implementation.

We can also just + strings together (as god intended)

We can evaluate expressions in ${} (ruby style?)

backslash to escape as usual

triple " works like triple ` in go

Number Types

Integers (and Longs) do integer things. Floats (and Doubles) do floating point things

We can do Int.MAX_VALUE, which hints that these are really classes

Kotlin infers number types. It will figure out that they need to be Longs or Doubles if they are too big. Inference follows Order of Operations.

Booleans

Booleans doing boolean things

&& gets evaled first, then || (use parens if it's ever not obvious tho)

Repetition with while

We have both while (cond) { expr } and do { expr } while (cond). I feel like I never use the latter form, but language developers keep putting it in there.

This section also introduces short form arithmetic assignment (e.g. +=)

Looping & Ranges

Ranged for can do for (v in values)

There are range literals for integers using .. (inclusive) and until ([))

You can modify a range with step (e.g. 0..10 step 2). I would like to know how to define these for user defined classes.

There is also downTo to go the other way

All of these yield an IntProgression type

Strings function like arrays of characters where the characters are unicode code points. They can be iterated over with in.

We have a stdlib function repeat(n: Int) that looks like the equivalent of taking a ruby block. We will learn about it later.

The in Keyword

in if used outside of a for statement tests whether a value is contained within a range.

The in keyword is used for both iteration and membership. An in inside the control expression of a for loop means iteration, otherwise in checks membership

We can do !in to invert it

in can be used for substrings

Expressions and Statments

A statement has an effect but produces no result, an expression has a result.

Function calls are expressions

for is an example of a statement

The Unit type contains a single value called Unit

This is used for empty returns

Expressions are evaluated where they are, not evaled later

We have both prefix and postfix ++

Defining a var or val inside of a block scopes it to that block

II: Introduction to Objects

Kotlin is a hybrid object-functional language.

I am skeptical...

Objects Everywhere

Objects have vals and vars

Classes, Members, instances, so on and so forth

built-in types like Double and Boolan are objects

If you try calling one that needs arguments, the IDE will ask for those arguments.

lol

Earlier object-oriented languages used the phrase “sending a message” to describe calling a member function for an object. Sometimes you’ll still see that terminology.

some still do

Conversions are just member functions on builtins (e.g. .toDouble())

Creating Classes

Every object in your program has its own unique address.

I wonder what these addresses mean in a JVM world

We call methods "member functions". We didn't want to confuse functions and methods. The other kind are called "top level" functions.

We have this in classes but it's implicit, and considered bad form to include it if not needed

Properties

They can be val or var

An object that is declared as a val can still have mutable var properties

However an object that is declared as a var cannot be assigned to if it has val properties?

Instances are references to a given object.

Constructors

Constructors define arguments that are scoped to the class and not available outside of it. If you want to make them accessible (as properties?) use val or var

To override functions we must explicitly say override

You only get one by default

toString() does what you think it does

Constraining Visiblity

We have public, private, protected, and internal

public does what you think it does. It is the default if you don't specify anything.

private is only accessible from members of the same class. At the top level this means it is accessible for anything within this file.

Maintaining multiple references to a single object is called aliasing

internal means it is visible anywhere in the same module but otherwise not accessible. This book doesn't use internal. Modules are a higher level concept determined by your build system (and they are separate from packages)

Packages

import <package name>.<identifier> (as <name>)

We can use fully qualified names instead of import

The package statement must be the first non-comment statement in the file. package is followed by the name of your package, which by convention is all lowercase:

Kotlin allows you to choose any name for your package, but it’s considered good style for the package name to be identical to the directory name where the package files are located

Testing

Tests are important y'all

Kotlin supports infix notation with a special keyword modifying a function to allow a func b instead of a.func(b) (more on this to come?)

There's a lil testing library in this book

TDD is a thing

Exceptions

This atom covers the basics of exceptions as an error-reporting mechanism. In Section VI: Preventing Failure, we look at other ways to deal with problems.

intrigued

We can throw an exception, if uncaught you get a strace

Some stuff will return null instead, but code that could produce null must be checked before using the result (more to come)

You can throw a specific instance of an exception

Lists

Lists are part of the standard Kotlin package so they don’t require an import

make with listOf (no dedicated literal syntax)

index in with [i], you get an exception if it's not there

list is actually a List<T> and we can declare its type if it's unclear

List<T> is immutable, you can use mutableListOf to make a MutableList<T>, which has add(elem: T) (and presumably other shit)

You can slice a MutableList<T> to a List<T>

+= works on a List if it's var because it's the same as l = l + e (which overwrites)

Variable Argument Lists

figured we'd have to have variadic functions eventually

uses a keyword vararg, you can only have one and it's simplest if it's last

vararg yields an Array (Array<T> ?), which is a low level class required by Java (prefer List<T>)

You can make an Array with arrayOf

You can use the spread operator (splat) on an Array

// Varargs/SpreadOperator.kt
import varargs.sum
import atomictest.eq

fun main() {
  val array = intArrayOf(4, 5)
  sum(1, 2, 3, *array, 6) eq 21 // [1]

  // Doesn't compile:
  // sum(1, 2, 3, array, 6)

  val list = listOf(9, 10, 11)
  sum(*list.toIntArray()) eq 30 // [2]
}

If you pass an Array of primitive types (like Int, Double or Boolean) as in the example above, the Array creation function must be specifically typed. If you use arrayOf(4, 5) instead of intArrayOf(4, 5), line [1] will produce an error complaining that inferred type is Array but IntArray was expected.

That seems fucky, might have to ask about that shit

fun main(args: Array<String>) is how you get your args

Sets

A Set is like a C++ std::set<T>

has the same mutable vs immutable split as List<T>

a List<T> supports toSet() which returns a Set<T> (and also distinct() which returns a List<T> with no dupes)

Maps

Map<K, V> preeetty much is what you think it is

can index with [k], get keys with keys(): Set<K>, values with values(): Collection<V> and loop over it to get entries() Entry<K, V>

// Unpack during iteration:
for ((key, value) in constants)

I want to know more ^

You get null if you index with a key that DNE

getValue(key: K): V will throw if key DNE, getValueOrDefault(key: K, def: V): V returns def if key DNE

It’s possible to use class instances as keys in a Map, but that’s trickier so we discuss it later in the book.

intriguing, given the existing keys are class instances :think:

Property Accessors

When you define a property (i.e. a var prop (or val?) you automatically get a get(): V and set(value: V) that are called when you .prop or .prop=. You can override these as you like.

You can do arbitrary shit in your get() so you can make functions look like fields if you so desire (this is discouraged if the details are actually complex)

If the property is private then so are its accessors. You can also just declare one of the accessors private if you like (for example, RO field)

Property accessors provide a kind of protection for properties. Many object-oriented languages rely on making a physical field private to control access to that property. With property accessors you can add code to control or modify that access, while allowing anyone to use a property

So "fields" and "properties" are different. We can use the field keyword to access the field that corresponds to a given getter and setters property

III: Usability

Extension Functions

You can monkeypatch on functions fun ReceiverType.extensionFunction() { ... }

They're package scoped and can be imported from other packages.

Extensions can only access public stuff from the thing they are extending.

They have access to this and the same implicit nature as a regular member function.

Named and Default Arguments

You can use the names of parameters instead of their positions. Good style not to mix named and positional (but you can)

You can also give arguments defaults (which is particularly nice with named arguments)

Use trailing comma in parameter lists

This works with constructors too

Overloading

works pretty much the way you would expect it to

If a class already has a member function with the same signature as an extension function, Kotlin prefers the member function. However, you can overload the member function with an extension function:

when you have overloaded functions and default arguments, if you omit an argument, the compiler will select the function that doesn't specify that argument over one that does specify it with a default.

i.e.

fun foo(arg: Int = 0) {
  println("doesn't get called")
}


fun foo() {
  println("does get called")
}

fun main() {
  foo()
}

when Expressions

These are basically case or switch or w.e

the cases are expressions so you can do kinda arbitrary shit in there

when is an expression, and if you use it's yielded value then it must be exhaustive (i.e. have an else)

when expressions can overlap the functionality of if expressions. when is more flexible, so prefer it over if when there’s a choice.

This is a strange recommendation, I think that every if statement can be written as a when instead

when has a special form that takes no argument. Omitting the argument means the branches can check different Boolean conditions.

makes the recommendation above even stranger

Enumerations

enum class T {
  T1, T2, T3; // semicolon required

  // rest of class body
}

toString() is automagically defined, but we can add other methods as we like (and presumably override toString?)

Data Classes

Essentially just structs

automatically overrides .toString() to be simpler and .equals() to just compare fields

automatically defines .copy() which does what it sounds like it does

automatically defines .hashCode() so that it can be used as a HashMap<K,V> key

Destructuring Declarations

We can have Pair<T1, T2> and return that

we can do val(a, b) = myfun() if fun myfun(): Pair><T1, T2>

There's also Triple<T1, T2, T3> (which supports this)

Data Classes also support this (can't be named, only ordered)

implemented in terms of operators, component1(), component2(), etc.

Map entries (what you get when you loop) can also be destructured

Nullable Types

One possible solution to this problem is for a language to never allow nulls in the first place, and instead introduce a special “no value” indicator. Kotlin might have done this, except that it must interact with Java, and Java uses nulls.

null is bad mkay

You add a ? to the end of a type to allow for null of that type

If you reference members of a nullable type kotlin will not compile

You don't have to declare a nullable version of your type, it's just always there

You can avoid the compilation issue by checking if (thing != null) { thing.method /* safe */ }

Safe Calls & The Elvis Operator

?. will either call if the receiver is non null or no-op if it is

lhs ?: rhs yields lhs if lhs is not null, otherwise rhs

When you chain access to several members using safe calls, the result is null if any intermediate expressions are null

Non-Null Assertions

myval!! asserts that myval is not null and converts it from (potentially) nullable to non nullable. If the assertion is wrong, it throws NPE.

Avoid non-null assertions and prefer safe calls or explicit checks

Extensions for Nullable Types

You can write an extension that adds a member function to a nullable type

Take care when using extensions for nullable types

no shit, lol

Introduction to Generics

class GenericHolder<T>(private val value T) { /* body */ }

fun <T> identity(arg: T): T = arg

fun <T> List<T>.first(): T {
  if (isEmpty())
    throw NoSuchElementException("Empty List")
  return this[0]
}

Extension Properties

fun ReceiverType.extensionFunction() { ... }
val ReceiverType.extensionProperty: PropType
  get() { ... }

You have to define your own get()

You can make them generic

val <T> List<T>.firstOrNull: T?
  get() = if (isEmpty()) null else this[0]

If you aren't using the generic argument(s) you can use <*>, this means you lose the type info of the underlying type

break & continue

No goto

break goes to end of a loop, continue goes to the beginning

They're only available within loops (for, while)

you can label loops with @label to allow break and continue to jump to an outer loop

Consider alternative approaches, and choose the simpler and more readable solution. This typically won’t include break and continue.

IV: Functional Programming

Lambdas

Lambdas are what you think they are

list.map({ n: Int -> "[$n]" })

Kotlin can infer the type of the argument a lot of the time. You can also use it for the argument if there is only one.

If the lambda is the only or last argument then you can put it outside the parens, e.g. list.map { "[it]" } (looking very ruby like)

run will exec a lambda (or another function?) (ok run is complex, maybe more to come)

The Importance of Lambdas

lambdas are first class functions that can be passed around which makes them useful for FP

lambdas are closures, they work the way you think.

regular functions are also closures

Operations on Collections

List has a constructor that takes a count and a lambda and inits by running the lambda with the index

val list1 = List(10) { it }
list1 eq "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"

List() and MutableList() are not constructors, but functions. Their names intentionally begin with an upper-case letter to make them look like constructors.

ok, so it's not a constructor I guess

there's like a bunch of Enumerable style methods

Member References

You can use a member function or a member property as a lambda by passing Class::member. This works a bit like ruby's .map(&:method) it seems.

Top level functions are just ::function and constructors are ::Class.

Arguments are passed down in paramter order

Extension functions work like regular member functions

Higher-Order Functions

val isPlus: (Int) -> Boolean = { it > 0 } to hold a lambda as a value

val sum: (Int, Int) -> Int = { x, y -> x + y } with named args

recall that Unit is what we return if we don't do anything

a function can return a nullable type

Manipulating Lists

lhs.zip(rhs) creates a new list of pairs [(lhs[0], rhs[0]), (lhs[1], rhs[1]), ... ]

zip stops when one list runs out

takes an optional (Pair<L, R>) -> T and will return a List<T> instead

flatten() does what you think it does

flatMap() -> .map().flatten()

Building Maps

List<T>.groupBy((T) -> K): Map<K, List<T>> groups items by the key produced by the passed function

people().groupBy(Person::age) for example

List<K>.associateWith((K) -> V): Map<K,V>

List<V>.associateBy((V) -> K): Map<K,V>

getOrElse runs a lambda for its default value

getOrPut inserts (and returns) via lambda if not found

Map<K, V>.map(MapEntry<K, V> -> T)): List<T>

.mapKeys() and .mapValues() do what you think

Sequences

Converting Lists to Sequences using asSequence() enables lazy evaluation.

They support "stream" operations rather than eagerly doing the whole .map or w.e

Can't index into one

Essentially stores the operations to be done and delays doing them until someone asks for a value back out

There are two categories of Sequence operations: intermediate and terminal. Inter- mediate operations return another Sequence as a result. filter() and map() are intermediate operations. Terminal operations return a non-Sequence. To do this, a terminal operation executes all stored computations.

Sequences don't necessarily have a size, you can make an "infinite" one with generateSequence which has an optional "seed" and a lambda

fun fibonacci(): Sequence<Int> {
  // fibonacci terms
  // 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, ...
  return generateSequence(Pair(0, 1), { Pair(it.second, it.first + it.second) }).map { it.first }
}

println(fibonacci().take(10).toList()) // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

You can only iterate once through a Sequence. Further attempts produce an exception.

Local Functions

You can define functions in any scope, it doesn't have to be top level.

When you do they are scoped to where you declare them.

You can do all the normal function shit with em.

They can be anonymous (which makes it so their return does not fuck shit up)

a return inside of a lambda will return out of the surrounding function?

we can return to a label from a lambda, either identifying that label when we declare the lambda or referring to the function that calls the lambda

fun main() {
  sessions.any { session ->
    if (session.title.contains("Kotlin") &&
      session.speaker in favoriteSpeakers) {
      return@any true
    }
    // ... more checks
    false
  } eq true
}

... this seems pretty evil?

to pass around references to local functions create a :: reference to them

Folding Lists

Collection<T>.fold(R, (R, T) -> R): R accumulates an R from a collection

Looks like ruby's collect?

foldRight() does it in reverse order

reduce() is the same but instead of an initial value it just uses the first value of the list

runningFold and runningReduce produce a list of each step

Recursion

recursion is recursion

performance risks associated with Java are still there

we have TCO but it's opt-in, use the tailrec keyword

To use tailrec successfully, recursion must be the final operation, which means there can be no extra calculations on the result of the recursive call before it is returned.

compiler warns if you flag tailrec but this condition is not actually satisfied

fun fibonacci(n: Int): Long {
  tailrec fun fibonacci(
    n: Int,
    current: Long,
    next: Long
  ): Long {
    // base case
    if (n == 0) return current

    // recursive case
    return fibonacci(n - 1, next, current + next)
  }

  // essentially a "private" helper
  return fibonacci(n, 0L, 1L)
}

V: Object-Oriented Programming

Interfaces

Looks like Java interfaces.

When implementing a member of an interface, you must use the override modifier

class MyClass : MyInterface { /* body */ }

when you do listOf a bunch of things with the same interface, kotlin infers the type?

interfaces can declare properties too

enums can impl them either at the top level or at the level of individual instances of the enum

SAM is a Java thing, we have special syntax with fun interface Class { fun f(): }

You can fulfill a SAM with a regular class or with a lambda

You can just pass a lambda directly where a SAM is expected, no need to declare it with the type

Complex Constructors

magic init block is the constructor

Constructor parameters are accessible inside the init section even if they aren’t marked as properties using var or val.

If you define a val that isn't specced in the automatic constructor then it must be initialized exactly once within init

Kotlin allows multiple init sections, which are executed in definition order

this seems like a bad idea most of the time ^

Secondary Constructors

constructor kw allows us to define secondary constructors. They must have a unique signature.

They must call another secondary or primary constructor via this(*args)

Calling another constructor from a secondary constructor (using this) must happen before additional constructor logic, because the constructor body may depend on those other initializations. Thus it precedes the constructor body.

Inheritance

We use "base" class and "derived" class

package inheritance

open class Base

class Derived : Base()

You must be open to allow inheritance. Default is final, which cannot be inherited from (you can explicitly spec if you like)

extensions are inherited

derived classes don't get base classes private parts. protected is accessible from derived classes but not available to the wider world.

you must specifically declare a function to be override if it matches the base class (otherwise compiler error)

you must declare a function as open to allow derived classes to override it

use super.function() to call function() on the base class

Base Class Initialization

package baseclassinit

open class SuperClass1(val i: Int)
class SubClass1(i: Int) : SuperClass1(i)

open class SuperClass2
class SubClass2 : SuperClass2()

You have to actually call the base class constructor (it can be primary or secondary)

package baseclassinit

open class Base(val i: Int)

class Derived : Base {
  constructor(i: Int) : super(i)
  constructor() : this(9)
}

You can use super instead of this to call a base class constructor from a constructor block

After Kotlin creates memory for your object, it calls the base-class constructor first, then the constructor for the next-derived class, and so on until it reaches the most- derived constructor.

Abstract Classes

oh yeah, it's getting messy now

abstract class MyClass { /* body */ }

flag functions and properties with abstract too if you don't wanna provide a defn

abstract is the default for interface's properties and functions

no multiple inheritance from classes

abstract classes can contain state where interfaces cannot

you can inherit from multiple interfaces if you like

if you are inheriting multiple definitions of the same function (or property) (through multiple interfaces) then you must override them. you can spec with super you want by doing super<Class>

Upcasting

in the eyes of the kotlin designers, the upcast is the raison d'etre for inheritance at all

Indeed, in virtually every case where there’s inheritance without upcasting, in- heritance is being misused—it’s unnecessary, and it makes the code needlessly complicated.

otherwise prefer composition

when we upcast we slice down to the base and the compiler will enforce that

Polymorphism

Connecting a function call to a function body is called binding. Ordinarily, you don’t think much about binding because it happens statically, at compile time. With polymorphism, the same operation must behave differently for different types—but the compiler cannot know in advance which function body to use. The function body must be determined dynamically, at runtime, using dynamic binding. Dynamic binding is also called late binding or dynamic dispatch.

like C++ virtual

bring out the vtables!

there is a runtime cost to this

I'm surprised they even mentioned the runtime cost, a friggin vtable lookup has got to be completely negligible for the uses cases where kotlin is a good choice of language

Composition

prefer composition to inheritance, it is simple and flexible

Inheritance & Extensions

consider extensions as an alternative to inheritance + adding methods

I do not undertstand the value of this "interface by convention" segment. You are allowed to define the same extension on two different types... and then get nothing interesting out of it?

It's common to create a class as an adapter to by used by a library or w.e (using inheritance or composition)

A member function reflects the essence of a type; you can’t imagine the type without that function. Extension functions indicate “auxiliary” or “convenience” operations that support or utilize the type, but are not necessarily essential to that type’s existence. Including auxiliary functions inside a type makes it harder to reason about, while defining some functions as extensions keeps the type clean and simple.

you cannot override extensions (no dynamic dispatch)

Class Delegation

Sometimes we want a middle ground between composition and inheritance

Class delegation essentially allows you to implement an interface in terms of composition

interface AI

class A : AI

class B(val a: A) : AI by a

you can use this as a janky multiple inheritance

proceed with caution only after exhausting regular composition or inheritance

also a janky way to get around lack of open ness without copying a lot of code

Downcasting

we can "smart cast" with is. Any code within the scope of if(instance is Klass) will treat instance as type Klass

fun main() {
  val b1: Base = Derived1() // Upcast
  if(b1 is Derived1)
    b1.g() // Within scope of "is" check

  val b2: Base = Derived2() // Upcast
  if(b2 is Derived2)
    b2.h() // Within scope of "is" check
}

We can use it with when for good effect

fun what(c: Creature): String =
  when (c) {
    is Human -> c.greeting()
    is Dog -> c.bark()
    is Alien -> c.mobility()
    else -> "Something else"
  }

You cannot smart cast on a var because it's underlying type could change between reading and accessing

Concurrency is an advanced topic that we do not cover in this book

:c

instance as Klass yields instance typed as Klass if it actualy is a Klass, otherwise it throws

We can also use as? which just returns null instead (recall all the null checking rules)

We can also use is as a predicate to find shit in lists

Sealed Classes

Sometimes we don't want anybody to add to a class heirarchy

sealed class MyClass allows inheriting from MyClass only within the same file as MyClass

This allows us to do when (instance) { is TypeA -> expr; is typeB -> expr } exhaustively (no else case)

there is no sealed interface

you can inherit from something that inherits from sealed

MyClass::class returns the class object, which supports reflection including .sealedSubclasses()

Type Checking

this::class.simpleName gets the "basename" of the class

type checking all over the place becomes a problem eventually

sealed allows us to use exhaustive when

this example seems really contrived. I feel like I would have to see a "real" use case cause otherwise this just seems massively obtuse?

Nested Classes

Yo dawg, we heard you like classes, so we put classes in yo classes so you can... express a class as being constituent of or owned by a larger class without necessarily being an instance of that class

Nested classes have access to the nesting classes private parts, otherwise they are like any old class

Nested classes can be declared as private, but then you have to return them as something else if you have a method that returns one

You can have a local class inside of a function body or other scope. They are not accessible outside that scope (so you must return as something else). You can't have a local interface. Use these sparingly

enums can be nested (and enum items have .ordinal() which returns their "rank")

Objects

object creates a singleton class

there's one instance globally shared, be careful and use private if you don't want people touching it

objects can’t be placed inside functions, but they can be nested inside other objects or classes (as long as those classes are not themselves nested within other classes)

the parenthetical seems like a weird restriction? but w.e

Inner Classes

inner is a keyword for nested classes that gives them unqualified access to the containing class

regular nested classes cannot inherit from inner

You cannot have an inner data class (why?)

In an inner class you can use this@Klass to refer to the right this

I am unreasonably upset that it's not this<Klass> the way super is

An inner class can inherit another inner class from a different outer class

this seems bonkers

I once again need to see a non-contrived example to even understand why you would ever want this, lol

Classes defined inside member functions are called local inner classes

this is getting worse than JOIN, lol

these imply inner, otherwise they are the same as normal local classes

ah shit, here come the factories

and then of course at the end "try to avoid using this feature much", aces

Companion Objects

this is basically class << self

You could just use a nested object but then you have to qualify access to it all the time

You can name it if you like or just leave it at top level

ok this entire section is very poorly written. all the ZI examples are like the ultimate misuse of f() g() u() etc type shit. If you're just writing a small example it's fine to use placeholder names but when you expand this across multiple pages my tired eyes are totally lost. Might revisit when I have energy. tl;dr seems to be that companion objects obey the normal object rules?

VI: Preventing Failure

Exception Handling

Exceptions combine reporting, recovery, and cleanup

Exceptions bubble up the stack

You can throw any Throwable

Good to inherit from Exception

class Exception1(
  val value: Int
): Exception("wrong value: $value")
try {
  expr
} catch(e: ExceptionKlass1) {
  expr
} catch(e: ExceptionKlass2) {
  expr
}

finally does what you expect it to do

kotlin recommends not catching, throw exceptions generally for major issues

for type related stuff just write the check rather than catching

IO throws exceptions, retry

Check Instructions

runtime checking of constraints for clarity, they throw when not met

require(predicate: Boolean, lazymessage: () -> Any)

throws IllegalArgumentException if predicate is false, uses the lambda for a custom message

<T: Any> requireNotNull(value: T?, lazymessage: () -> Any): T

returns a non-nullable value if value is in fact not null, otherwise throws

You don't usually need the return value because value will be smart-cast in scope of the call

check(predicate: Boolean, lazymessage: () -> Any)

throws IllegalStateException if predicate is false, used for postconditions (this seems dumb, better to test)

assert exists from java but it needs a flag and also you shouldn't use it (lol)

The Nothing Type

Nothing is not the same as Unit, it is for a function that never returns (usually because it throws)

builtin TODO(msg: String) throws NotImplementedException with msg (cute)

Kotlin treats a throw as type Nothing, and Nothing can be assigned to any type.

seems weird written out but the examples explain, essentially a function that returns a real type or Nothing can be considered to return the real type (if it is in the Nothing branch it isn't gonna return)

When given a plain null with no additional type information, the compiler infers a nullable Nothing

Resource Cleanup

fun <T : AutoCloseable?, R> T.use(block: (T) -> R): R

AutoCloseable is a java interface, it requires only void close()

use() wraps block and guarantees a close(), it reraises any exceptions it encounters

useLines() reads a file and yields a Sequence<String> to the passed block (and guarantees closing)

forEachLine() reads a file and yields each line to the block (and guarantees closing)

patterns like these are encouraged to avoid having to manually do a bunch of finally shit

Logging

Logging is good

You must create a logger before using it. You’ll almost always want to create it at file scope so it’s available to all components in that file:

interested to see if that holds up in bigger projects ^

https://github.com/MicroUtils/kotlin-logging FOSS logger for kotlin

Unit Testing

Of the numerous unit test frameworks, JUnit is the most popular for Java. There are also frameworks created specifically for Kotlin. The Kotlin standard library includes kotlin.test, which provides a facade for different test libraries. This way you’re not limited to using a particular library. kotlin.test also contains wrappers for basic assertion functions

gotta pull in org.jetbrains.kotlin:kotlin-test-common to use it?

Kotlin supports annotations for definitions and expressions. An annotation is the @ sign followed by the annotation name, and indicates special treatment for the annotated element.

You can use spaces in function names if you wrap w/ `

JUnit requires some setup (gotta learn how packages work)

IDEA will do this for you

VII: Power Tools

Extension Lambdas

val vb: String.(Int) -> String = {
  this.repeat(it) + repeat(it)
}

The Kotlin documentation usually refers to extension lambdas as function literals with receiver. The term function literal encompasses both lambdas and anonymous functions. The term lambda with receiver is often used synonymously for extension lambda, to emphasize that it’s a lambda with the receiver as an additional implicit parameter.

You can pass an extension lambda to a function that expects an ordinary lambda, as long as the parameter lists conform to each other:

Polymorphism does what you want it to

These are often used for builders cause they "yield" the thing being built as this, which is super convenient

we call these "builders"

Scope Functions

let yields the thing you called it on as it and returns the last expr in the block (this is functionally Kernel#then)

run yields the thing you called it on as this (using extension lambdas I suppose?) and returns the last expr in the block

with is run but it's with(T) instead of T.run

also yields the thing you called it on as it and returns the (modified) object (this is functionally Kernel#tap)

apply yields the thing you called it on as this and returns the (motified) object

you can use ?. to ensure that the scope function only gets called if the receiver exists, and this functions to null check the receiver w/in that scope (doesn't work with with)

you can do expr ?: return to short circuit

if you nest them then the innermost becomes this or it

nesting these is kinda questionable

When the compiler sees an inline function call, it substitutes the function body for the function call, replacing all parameters with actual arguments.

this must be a Kotlin feature? can inline be ignored by the compiler?

Creating Generics

Generics are complex, some of it is beyond this book

They work well for dealing with potential different behaviour that can't be identified by an interface or a class hierarchy

Any is the root of all classes

Any is mostly used to downcast

fun <T> f(arg: T): T = arg

class GClass<T>(val x: T)

containers are a common use case for generics

use <T: Base> to require that T 'is a' Base

the upside over polymorphism is that we can return the exact type, not the upcast

you cannot test the type of a generic type at runtime because of type erasure (a Java constraint)

reified for generic functions allows you to preserve this information (equivalent to just passing the class as an argument)

To use reified, the function must also be inline

<out T> is covariant. you can upcast a covariant Container<T1> to Container<T2> if T1 is a T2. it can return T but not accept it

<in T> is contravariant. you can upcast a contravariant Container<T2> to Container<T1> if T1 is a T2. it can accept T but not return it.

<T> is invariant. you cannot cast to any other <T>

Functions can have covariant return types. This means that an overriding function can return a type that’s more specific than the function it overrides:

Operator Overloading

Kotlin allows you to override operators, but only certain pre-defined ones

+ is operator fun T.plus(rval: T): T

operator implies infix, you don't have to spec it

You can declare as an extension or as a member, members access private parts

== -> equals which takes Any? and must be a member

When overriding equals() you should also override hashCode(). This is a complex topic, but the basic rule is that if two objects are equal, they must produce the same hashCode() value. Standard data structures like Map and Set will fail without this rule. Things get even more complicated with an open class because you must compare an instance with all possible subclasses

lol, IDEA will generate them for you

=== checks whether two objects refer to the same memory (and cannot be overriden?)

I'm not gonna note every overrideable operator, there's a list in the book and they're most straightforward

operator fun T.compareTo(other: T): Int defines all the other comparison operators (but not ==)

operator fun T.rangeTo(other: T): R<T> allow defining ranges

contains overrides in

invoke allows you to make a thing "call-able". you can have multiple

Kotlin allows spaces, certain nonstandard characters, and reserved words in a function name by placing that function name inside backticks:

Using Operators

You don't have to overload operators much but a lot of the stdlib uses them

compareTo() was introduced as a standalone extension function in Operator Over- loading. However, you get greater benefits if your class implements the Comparable interface and overrides its compareTo()

Prefer implementing Comparable

you can overload componentN for custom destructuring

Property Delegation

val/var property by delegate

for a val the delegated thing must have getValue()

for var it needs getValue() and setValue()

class ReadWriteable(var i: Int) {
  var msg = ""
  var value: String by BasicReadWrite()
}

class BasicReadWrite {
  operator fun getValue(
    rw: ReadWriteable,
    property: KProperty<*>
  ) = "getValue: ${rw.i}"

  operator fun setValue(
    rw: ReadWriteable,
    property: KProperty<*>,
    s: String
  ) {
    rw.i = s.toIntOrNull() ?: 0
    rw.msg = "setValue to ${rw.i}"
  }
}

the delegated-to class is "bound" to the delegating class, so it has access to all it's internal bits

you can impl an interface to communicat this

the ReadOnlyPropert<T, V> can be used for a SAM conversion

you don't need to use an exact T, you can specify a hierarchy including Any?

KProperties<*> can be used to figure out some details about the property that has been delegated to you (for example, it's name)

Property Delegation Tools

MutableMap<K, V> can be used as a delegate for properties right out of the box

kotlin.properties.Delegates has some cute things that can check/watch/prevent updates

Lazy Initialization

val lazyProperty by lazy { initializer } means lazyProperty is only initialized when first called

Late Initialization

lateinit allows you to specify that a property of the class will be initialized later

there are a bunch of restrictions, notably it must be var and it must not have custom get() or set()

::prop.isInitialized will tell you whether it has been initialized

this seems hard to justify, but w.e

Appendix B: Java Interoperability

Calling Kotlin from Java

You just import classes and then init them

JavaBean-style getters and setters in a Java class become properties in Kotlin

When working with Java, the package name must be identical (including case) to the directory name. Java package names typically contain only lowercase letters. To conform to this convention, this appendix uses only lowercase letters in the interoperability example subdirectory name.

extensions are a good choice to fixup java stuff

Calling Kotlin from Java

properties become private fields with getters and setters (if var)

member functions become Java methods

If a Kotlin class is in the same package as Java code, you don’t need to import it

data classes generated methods work in Java

You need to include kotlin-runtime.jar if you're going to call Kotlin from java

top level functions become static methods on a class named for the filename, TopLevel.kt -> TopLevelKt (why include the kt?)

you can import static to use the name unqualified

There is an annotation to change the name of the generated class if you like

Adapting Java to Kotlin

You can use extensions to make Java utilities feel more like their Kotlin counterparts, esp operator overloading

Java Checked Exceptions & Kotlin

checked exceptions are considered a mistake by the Kotlin team

there is a @Throws annotation if you must specify what you throw for interoperability reasons

Nullable Types & Java

If a type comes from Java, the Kotlin compiler cannot necessarily check it at compile time for nullability

This means that you can assign the result of a Java function that returns Type to Type in Kotlin rather than Type? even when it might be null

You might throw NPE at assignment to a non nullable type because Kotlin will check there (at runtime ofc)

The proper strategy when calling un-annotated Java code is to avoid type inference, and instead understand whether or not the code you are calling can produce nulls.

Nullability Annotations

There are annotations for the Java side using @Nullable and @NotNull to make type inference work magically on the Kotlin side

There's different annotations for different platforms, check the docs

Collections & Java

Kotlin largely implements its collections in terms of Java ones for improved interoperability

Biggest departure is that Kotlin defaults its collections to immutable through an interface

You can cast to and/or use the Java collections directly but it produces a warning

recall: just because you don't have the ability to modify the underlying data doesn't mean it cannot be modified

Java Primitive Types

In the JVM anything with new goes on the heap, a small set of primitives go on the stack

Kotlin does not distinguish between on the stack primitives and on the heap primitive wrappers (e.g. Int vs int in Java)

The Kotlin compiler will sometimes put (non-nullable) primitive types on the stack, but you don't really need to worry about it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment