Wed 4th - Fri 16th Dec 2019
Table of Contents
- To Do
- Presentation Ideas
- Refactoring to Kotlin
- Building PWAs in Kotlin
- Coroutines! Gotta catch em all!
- What the f(p) is Kotlin?
- Your first server with Ktor
- Firefox for Android
- I walk the line
- Failure is not an option, Error handling strategies for Koktlin programs
- Creating full-stack web apps with Kotlin DSLs
- The power of types
To Do
- Watch last year’s talk about Kotlin expressions vs statements.
- Look at Duncan’s talk about Kotlin performance measurement.
- Look at Contracts.
- Look more into when primitives are boxed or not (look at decompiled Java).
- Look into whther
10
is generated as anint
or abyte
. - Watch the first part of Building PWAs in Kotlin.
- Watch “The state of Kotlin/JS” (more about the build system).
- Look into Kotlin
dynamic
. - Look at the Arrow library.
- Look at Intuit Kotlin blog/whitepaper.
- Does Kotlin data class copy do a deep copy on immutable objects?
Presentation Ideas
- Kotlin Lists
- Mutable vs non-Mutable.
- Interop with Java.
- asList vs toList.
- Conversion hard edges
- Lombok.
- Optional.
- Misc
- Java bytecode generation annotations. (
@JvmStatic
,@JvmField
) Unit
vsvoid
.internal
vspackage
.- Java interop.
- Defaults to
public
notpackage
. firstOrNull { /* thing */ }
- Nullable things?
- Generated nullable checks.
- Auto boxing of primitives.
- Kotlin/JS?
- The
inline
keyword.
- Java bytecode generation annotations. (
Refactoring to Kotlin
Tools > Kotlin > Configure Project in Kotlin.
Tools > Kotlin > Configure Kotlin Plugin Updates > turn on new converter. (This was later disabled since it didn’t work as well.)
When converting a project, just start with where active development is. Functional code in Java is verbose and it gets much better in Kotlin. Java/Kotlin interop can sometimes be messy, so try to avoid creating a messy interface.
Kotlin has a separate reflection library to Java (it is optional). Ideally with Kotlin you’ll have less reflection. There are gotchas reflecting into Kotlin classes from Java. You can fine tune (with annotations) how the Kotlin compiler generates Java bytecode.
Part 1
Presenter.java
Kotlin doesn’t have direct access to fields (it does have the automatic generation of getters/setters).
Therefore Java classes that use your newly converted Kotlin class will need to have .field
turned into .getField()
(if the original class had a public field
).
Kotlin has named parameters. For this reason you should keep the same parameter names when overriding methods.
You only need a { }
if there’s anything inside it (except for try/catch
), so you can leave them out if empty.
data class Presenter(val name: String)
Stuff with immutables and deep equality (functional stuff) is much better in Kotlin (so the data/domain layer is a good place to start).
Lombok and Kotlin don’t play nice together :-(.
Session.java
this.presenters = Collections.unmodifiableList(new ArrayList<>(presenters));
This is doing a defensive copy of the parameter (in Java).
init
blocks are run as part of the constructor in every class.
Collection interop is “clever”.
List
is a Kotlin type and the compiler maps them onto Java collections.
A Kotlin List
is a view of a list that doesn’t let you modify it - standard Kotlin functions will give you immutable lists.
So in a pure Kotlin world, we could get rid of the snippet above, but unfortunately when Java uses the list, it doesn’t see the immutability (so the defensive copy is still useful).
asList
will give you a view into the underlying data while toList
will create a copy.
fun withTitle(newTitle: String): Session {
return Session(newTitle, subtitle, slots, presenters)
}
But since we’re using a data class, we can use copy
(and named parameters).
fun withTitle(newTitle: String): Session {
return copy(title = newTitle)
}
And then:
fun withTitle(newTitle: String) = copy(title = newTitle)
This is a single expression style function. The return type is inferred, but it’s worth including if it’s non-obvious or nullable.
But for this method specifically, once we no longer have Java clients we’d get rid of them and let the clients use copy
themselves.
(It’s messier in Java due to lack of named parameters).
Interop can be painful if you use a lot of Optional
in Java.
The void
return type is called Unit
.
In Java there’s a difference between Callable
and Runnable
because of returning void.
Kotlin deals with this much more nicely.
SessionTests.java
Kotlin has internal
instead of package
.
Kotlin still has packages, but they’re purely for avoiding naming collisions, not visibility.
(In the bytecode it becomes public
with some metadata.)
@Test
fun `can change parameters`() {
// ...
}
(You probably shouldn’t do this in production code, just in tests.)
If you’re not returning anything, you can leave : Unit
out.
fun Session.withPresenters(vararg newLineUp: Presenter) =
copy(presenters = newLineUp.toList())
This is an extension method, it can be called on objects of type Session
.
There is an implicit this
in the body - which may be null.
You can place this in the test file instead of the implementation file.
The extension method is chosen based on the compile time type (there’s no runtime polymorphism).
In IntelliJ, extenion functions are in itallics.
Part 2
Sessions.java
Kotlin object
s are singletons.
You normally don’t need them because you can have top level definitions for functions of variables.
One case you may want to use them is when you want polymorphism.
?.
is the same as ?:
except the right hand side is null
.
Kotlin has stream support built in.
Kotlin has an inline
keyword which effects semantics (eg, a quick return from a lambda expression).
You can write your own control structure.
(You’ll need to recompile code to pick up new dependencies if they’re inline
.)
It can avoid boxing.
Extension functions and inline
means you don’t need macros in the language.
sessions.firstOrNull { (title) -> title.equals(exp) }
sessions.firstOrNull { it.title.equals(exp) }
The (title)
is destructuring the it
passed into the lambda and taking the first member of the data class.
You probably should only do this for Pair
s.
typealias SessionList = List<Session>
fun SessionList.findWithTitle(title: String): Session? =
firstOrNull { it.title.equals(exp) }
Part 3
JsonFormat.java
If you have Class A
that uses static methods on Class B
, it’s better to convert Class A
first, because otherwise it will be changed when Class B
’s static methods are changed to instance methods on the object.
All Kotlin exceptions are unchecked.
Kotlin says that if the last parameter to a function is a function type and you’re using a lambda, you can put that lambda outside of the brackets.
The names of extension functions can be much shorter because they work in some context.
Extension functions also make working with null nicer - you can use thing?.doThing
as opposed to having your separate doThing
function have to deal with taking a nullable parameter.
val text = node.asText()
Where node
is a Java class.
text
will be typed as String
, not String?
and the compiler will insert a check to make sure that text
is not null
.
The Kotlin compiler will insert null checks on non-null parameters to public methods (but this can be turned off).
Json.java
You can define a function with syntax like (Presenter) -> JsonNode
.
You can create infix functions with the infix
keyword.
They can still be called as a normal method.
The apply
and let
extension functions make it easier to turn everything into expressions.
You can use the elvis operator to throw:
val a = b!!
val c = d ?: throw ValueMissingException()
Misc
Kotlin will auto-magically deal with the differences between primitives and reference types.
Int
will normally be a primitive integer and will remain as such if it’s passed to an inline
generic function.
It will get turned into an object if it has object methods called on it (eg, toString
), or if it is Nullable.
fun fred() {
10.let {
println(it)
}
}
10
would be kept as a primitive.
val things = intArrayOf(1, 2, 3)
things.forEach {
println(it)
}
val things = intListOf(1, 2, 3)
things.reduce { x, y -> x + y}
Neither of these box the integers (because the methods are inline
).
(This breaks if you call something like toList
.)
Kotlin’s support for function composition and syntax for blocks means you need less/lighter frameworks which helps performance/start up time. (Compared to Spring/Java EE.)
A few gotchas in the Kotlin standard library - there’s a toString
method on Any?
which prints “null” on null
.
println(null?.toString())
println(null.toString())
This can be confusing when searching through logs.
In Java you can write functions overloaded on primitive and the boxed value. These are hard to use from Kotlin.
Sequences
are lazy versions of Iterables
.
Benefits of Kotlin: Structural benefits - it encourages thinking in a functional and immutable way. Predictability - Java teams spend a lot of time debugging in prod (NPEs). They don’t have to track down where null references come from - this is built into the type checker and the design. Anecdotally there’s greater trust put in the teams using Kotlin (Nat).
Java with lambdas, immutable data, functional style is very amenable to converting to Kotlin. Because of the easiness of using immutable data structures, you get rid of the problem of aliasing (someone else from modifying something underneath you).
It’s fairly easy to get Java programmers to write Kotlin - approx a week of pair programming to get them up to decent (Nat).
For error handling, find an Either type. Reserve exceptions for things when you’re fundamentally broken. Try to lift the potential for errors out of the logic.
The Kotlin converter and compiler understand @NonNull
and will help them produce better code.
Building PWAs in Kotlin
Erik Hellman. Slides.
Uses kotlinx.html
.
Kotlin JS can work nicely with React, npm("@jetbrains/kotlin-react"))
and npm("@jetbrains/kotlin-react-dom"))
.
npm install -g create-react-kotlin-app
npx create-react-kotlin-app demo
With this approach you’re not using Gradle.
JS Dependencies
How do you add dependencies on JS libraries?
kotlin {
target {
nodejs()
browser()
}
sourceSets["main"].dependencies {
implementation(kotlin("stdlib-js"))
implementation(npm("jszip", "3.2.2"))
}
}
Theny ou have to declare the API.
external class ZipObject {
fun asnc(type: String): Promise<Any ?>
}
This will work nicely with coroutines and such.
You may need to use the dynamic
type for JavaScript types that can’t be represented in Kotlin.
Dukat
Requires the .d.ts
files for a library.
It will generate a Kotlin interface for that library.
Union types in function parameters will be turned into overloaded functions.
It is experimental.
Conclusion
Is Kotlin/JS ready for production use? “It depends…”
The build system will build a single JS file (so you’ll need to do fun things to output a service worker, eg multiple modules). JS output can be very big.
Questions
The new Kotlin/JS build system wraps WebPack and you can pass in configuration options.
What’s the difference between Any
and dynamic
?
You can try to use Any
and if that doesn’t work, use dynamic
.
Is there a place to find supported 3rd party libraries? kotlinlang.org has some stuff, but it’s somewhat out of date.
Can you talk a bit more about the size of the output JS? Not much data, but adding coroutines is normally the problem.
Is there a way to go from Kotlin to Typescript? Yes, you can publish Kotlin/JS npm packages, although unsure about whether you can do TypeScript type definitions.
Coroutines! Gotta catch em all!
By Florina Muntenescu.
About cancellation and exceptions of coroutines.
The basics
CoroutineScope
helps you keep track of them, cancel them and be informed of failure.
val scope = CoroutineScope(Job())
val job = scope.launch {
}
A Job
is a handle to a co and provides the lifecycle.
Cos can be active, completing or finished, cancelling or cancelled.
Finished means all of its children are also complete.
Cancellation
A co inherits the context from its parent.
New -> Active
Active -> Completing
Completing -> Complete [label="children finished"]
Active -> Cancelling [label="cancel / failure"]
Cancelling -> Cancelled [label="children cancelled"]
val scope = CoroutineScope(parentJob)
val job1 = scope.launch {}
val job2 = scope.launch{}
scope.cancel()
This will cancel all of its children.
A cancelled child doesn’t effect its syblings.
Cancellation of coroutine code needs to be cooperative.
You need to use job.isActive
or ensureActive
(throws an exception if not active).
yield
will also throw a CancellationException
if the Job is cancelled.
delay
has this built in.
If you call cancel
before join
the state will be cancelled (but everything works fine), if you call join
first it will be completed.
You use await
when you need the result of the co.
If you call cancel
before await
you’ll get a JobCancelledException
on the await
.
val job = launch {
try {
work()
} catch /* or finally */ {
println("Performing cleanup")
withContext(NonCancellable) {
// Perform stuff here that you want
// to be immune to cancel.
// Eg, you couldn't call delay in the
// parent scope because it would
// break out of execution.
}
}
}
TL;DR
- Cancellation needs to be cooperative.
Failure
When a co throws an exception.
If a child fails, the parent will cancel all of its other children and then itself. A cancelled scope cannot start more coroutines. Eg, if you’re in a UI related scope, if one child throws it will kill all the other parts of the UI.
There is also the SupervisorJob
where failure or cancellation of a child will not effect other children.
This means that the exception propagates up to the parent.
It only works for the immediate children (not grandchildren).
What the f(p) is Kotlin?
By Shelby Cohen and Katie Levy.
A talk about Intuit converting to Kotlin. One of them worked on TurboTax & and Android part, now on the backend servers. One’s on the developer logistics side of stuff.
Intuit has 9,000 employees, has 50 million customers.
Kotlin was introduced in 2018 and now it’s used all over the place.
Steps (for change): 1) Find your passion 2) Socialize (talk to eng leaders) 3) Proof of Concept (that addresses people’s concerns) 4) Create a plan (with timelines) 5) Pursuade with data 6) Deconstruct myths 7) Can’t Stop, Won’t Stop 8) Connect with a larger community
They started with converting data classes and created documentation along the way. Convinced cynical Java-lovers with presentations on new features, group coding and group training.
Useful Kotlin features:
- Null safety.
- Top crash type across 100+ applications.
- How concise null checks are.
- (There were many more, I didn’t catch them).
They chatted with eng leaders to get a list of hesistations and then created a PoC to deal with that. This included data classes, functional programming.
Create a Plan
In their plan they changed (in order): 1) Test classes. 2) Data classes. 3) New features. 4) Then measured the impact.
Pursuade with Data
- Kotlin website says 40% fewer LoC.
- They gave examples of how Kotlin cut down boilerplate code.
- Emphasise the auto-convert tools (and subsequent auto-correct tools).
- Companies currently using Kotlin (eg Google…)
Deconstructing myths
Wrote a technical whitepaper that explained what problems they were solving. They made an external whitepaper.
- Proving that IDE support and such is not going to lose out.
when
expressions help encourage immutability (?).- Lists are immutable by default.
They also used Arrow, a “functional companion to Kotlin’s Standard Library” eg, Option
, Try
, Either
, IO
, Applicative
, Monad
.
(How big is Arrow?)
map
and flatMap
operate on the right hand side of Either
.
Either
can be used instead of throwing exceptions.
You define the success state at “the lowest layer of the code”.
Key benefits from Arrow:
1) Status checks at lowest layer. 2) Handle errors with types, not exceptions. 3) Avoid mutation.
Can’t Stop, Won’t Stop
They started a weekly lunch learning community to talk and learn about Kotlin (eg, live coding, code reviews, reading Kotlin in Action).
Your first server with Ktor
By David, Big Nerd Ranch.
Ktor is a Kotlin-based framework to build and consume HTTP-based web services.
You need to have the Ktor plugin (by JetBrains) installed. This will set the project up with the required dependencies.
Ktor has a HTML DSL (and a CSS DSL).
An Application is the primary building block of a Ktor web service.
It is configured in application.conf
(written in HOCON) and you can add modules in Application.kt
.
A module is represented by a static method in the Application.kt
.
This file contains a main
function and some modules.
@Suppress("unused") // Used in application.conf
@kotlin.jvm.JvmOverloads
fun Application.module(testing: Boolean = false) {
routing {
get("/") {
call.respondText(
"Hello world",
contentType = ContentType.Text.Plain
)
}
}
}
To return something more fancy, use the HTML DSL.
get("/characters") {
call.respondHtml {
body {
h1 { + "Characters" }
ul {
for (n in 1..10) {
li { + "Character $n: ${Character()}" }
}
}
}
}
}
Manual and names file
Firefox for Android
Why
The application is almost 10 years old. Then they built Firefox Focus. Later a different leam developed Firefox Rocket (became Firefox Lite). Then they created Firefox for Fire TV and then Firefox for Echo Show.
Goals:
- They wanted to share code between apps.
- Reduce maintenance overhead.
- Clear architecture boundaries.
- Make it easier & faster to build new/experimental products.
Solution:
- Independent Android libraries.
- Extensible application architecture.
- Minimal cross component/3rd party deps.
- Customizable and pluggable.
- Distributed as AARs via Maven.
They are written from 100% Kotlin from the beginning. (But with some Rust for cross-platform things.)
Components
The BrowserEngine has a network stack and takes JS, HTML, CSS and displays them. On Android, the most obvious choice is WebView. Mozilla has GeckoView which is distributed as a library. (There is also Servo, written in Rust.)
GeckoView advantages:
- Developer choses the version.
- Multiprocess (websites are isolated).
- Separation of session/tab and view (you can load websites off screen).
- Private mode (WebView had this, but no more).
- WebExtensions (some support).
- Tracking protection.
Another component is the browser Toolbar.
FeatureComponents know how to connect other components together.
Other features required (each gets a component):
- Downloads (and in progress notification).
- Context menu.
- Toolbar auto-complete.
- Media notification.
- Reader mode.
Useful Kotlin features
Explicit nullability.
Handling State - they’re using a Redux style thing for state.
They have a data class BrowserState
which is a tree.
The components observe parts of the state.
The components dispatch a sealed class that goes to a reducer (they use a when
clause and the compiler makes sure they don’t miss a class).
This was made more readable with type aliases.
I walk the line
A talk about what to conver tto Kotlin and what not to. The context is using Kotlin for multi-platform.
AirBnB talked about their experience with React Native - they found some should-be trivial things were horrendous and it was hard to track down the cause. You need at least 2 people to debug every problem (eg an Android developer/a JS developer/someone who understands the framework).
When building something cross platform, you can’t forget about developer experience (they’ll leave!).
Kotlin/Native
In this case, it’s more likely that the Android developer would know Kotlin.
Android and iOS have different designs and paradigms about user interaction so write once/run everywhere creates bad apps. Kotlin/Native is focused on your business logic, not the presentation.
Porch Pirate Protector
A locked box that you can remotely lock and unlock. They used Kotlin/Native for the:
- iOS App
- Android App
- Server
- Raspberry Pi
MVA - Model-View-Anything Else. They used Model-View-Presenter (used by the 2018 kotlinconf-app).
Model: data class, View: interface, Presenter: class. Having the view being an interface is good for multiplatform, Fragments on Android, UIViewController on iOS, mocked for testing.
Kotlin/Native uses something like automatic reference counting for GC.
MVP became MVSP - Model-ViewState-Presenter. A View isn’t a live object, it’s a single object that is returned from the presenter. It became another data class. Data went from View Controller to the Presenter which returned more state to the View. This is unidirectional data flow (it makes Views simple). This suits SwiftUI.
Sharing information
They wanted to share style info between platforms. This doesn’t really work since colours are in XML in Android and assets on iOS. They ended up generating the XML/assets from a common source.
Libraries
ktor
and kotlinx.serialisation
work together really well, combining networking and parsing.
Just annotate something with @Serializable
.
(Concurrency on Kotlin/Native can be complicated, there’s a talk about it.)
On Android you can use LiveData along with Coroutines and Flow.
To figure out which parts of your app should be Kotlin Native, consider why are you using Kotlin/Native in the first place? You can’t totally ignore the platform just because you’re using a cross platform framework.
Summary
- Kotlin/Native is a double edged sword.
- You can move much faster on some things.
- Be mindful of the trade-offs you’re making.
- Prioritise developer experience.
- Remember your reason for Kotlin/Native in the first place.
Q & A
How can you sell this to an iOS developer? You just add a build script run phase in XCode (it’s just like bringing in a library). It lets the iOS developers focus on the iOS specific parts of the application (not the business logic).
You mentioned that Kotlin/Native was better for your business logic over UI? I don’t think it was designed that way, but it is well suited for that. JetBrains are really good for tooling and design, but don’t deal with the UI parts of the frameworks.
Can we marry Flutter and Kotlin/Native? At AppDevCon someone did a talk about this - it was difficult. UI should be platform specific, business logic can be cross platform.
What’s your experience with Kotlin/Native and platform capabilities like Bluetooth or Location? Don’t have much experience. iOS and Android have very different systems for location.
Can Kotlin/Native work with C++? They don’t know.
How mature is the build system? With 1.3 they released a good tutorial for this.
Failure is not an option, Error handling strategies for Koktlin programs
Nat Pryce & Duncan McGregor
Kotlin is a bit conflicted in how you deal with failures.
Causes of failure:
- Invalid Input
- External Failure
- Programmer Errors
- System Errors
Many crashes are caused by the incorrect handling of a recoverable failure. We may not take into account that an operation can fail. Knowledge of how to fix an error may be far from where the error occurs.
Java
Java tried to help with checked exceptions:
- CheckedException - the program should be able to recover and the type checker makes sure the programmer does so.
- RuntimeException - a programmer made a mistake that was detected by the runtime.
- Error - JVM is buggered, all bets are off.
But people didn’t like this. RuntimeException became a subclass of Exception. It wasn’t applied consistently. Popular frameworks wrapped checked exceptions in runtime exceptions.
Higher order functions made checked exceptions more complicated.
Kotlin
Kotlin has no checked exceptions (they’re a Java feature, not a JVM feature). But we lose our ability to type check our error handling code.
What’s the best way to handle errors in Kotlin? It depends… The best way depends on performance constraints, team preferences, surrounding architecture (and these things may change during the life of the project).
Exceptions
They’re easy to throw, but a bit verbose to catch. We tend to translate errors as they cross domain boundaries.
But without support of the type checker we don’t know if we’ve caught all types of exception. Fuzzing can help you check you have coverage here (presenter library: snodge ).
Exceptions are fine when the behaviour of the program does not depend on the type of the error, eg:
- It may crash.
- It may print to stderr.
- It may display a dialog and let the user correct the problem.
(Although do use JVM exceptions where appropriate, eg when an index is out of bounds, illegal argument, since they have useful context.)
Avoid Errors
The problem with errors is that they can contaminate - eg if my data has an error parsing, what do we do with the entire request?
We could write functions that can’t fail.
fun readFrom(uri: String): ByteArray? {
}
This is a partial function that’s only valid over a certain range of inputs.
fun readFrom(uri: URI): ByteArray? {
}
Now it’s a total function (since uri parsing can’t fail). This lifts the error handling up.
Null to represent errors
Null is in the language, Kotlin’s type checker will keep you honest and there are nice language feature to deal with nulls. The downside is that there’s no information conveyed with the error, and we’re using the same construct for representing errors and absence of a value. It’s good for small, very local errors.
Using nulls is fine when:
- Cause of error is obvious from context.
- Optionality and errors are not handled in the same code.
Move errors to the outer layers
Pull IO up into upper layers, make dealing with the data total functions.
fun process(src: URI, dest: File) {
val things = readFrom(src)
process(things, dest)
}
fun process(src: URI, dest: File) {
val things = readFrom(src)
dest.writeLines(process(things))
}
Here we’ve moved opening dest
out of process
.
Algebraic data types
In Kotlin, this is a sealed class hierarchy.
sealed class Result<out T, out E>
data class Success<out T>(val value: T): Result<T, Nothing>()
data class Failure<out T>(val reason: E): Result<Nothing, E>()
This forces the programmer to consider the possibility of failure when you try to get the value.
when (result) {
is Success<Value> -> doThing(result.value)
is Failure<Error> -> doOtherThing(result.reason)
}
We can then defined extension functions on the class hierarchy (eg flatMap
, mapSuccess
and mapFailure
) that makes working with these more pretty.
This can get ugly if a stage of the pipeline requires results from an earlier stage (other than the previous). The Arrow library can help with this.
Error reason representation
- You could represent them as an Exception.
- You could have a hierarchy of data classes.
- A single hierarchy (but you lose exhaustiveness of error checking).
- Many smaller hierarchies for small contexts (but this is more work) - an enum may be fine.
Results are fine when:
- Your team is fine with functional programming.
- You don’t need stack traces.
Design your systems to be robust to errors
Eg, transactions to prevent inconsistent state. Avoid mutating shared state. Queues and retries.
Differentiate between environmental failures (it’s worth retrying) and logic failures (you’ll get the same mistake).
Overall recommendation
- Null for simple parse errors.
- Result to report the location of complicated parse errors.
- Result for explicit errors from app logic.
- (many more, find slides…).
Creating full-stack web apps with Kotlin DSLs
Pamela Hill
Project Intro
A Kotlin full-stack web app. It’s a vision board called Life Visualizer. A vision board is a collage of images that inspire you. You put in a goal and it uses Unsplash to find images for that goal.
A Webapp written in Kotlin/JS that uses Axios library to talk to Ktor server which uses a Ktor client to talk to Unsplash. The server also saves data using the Exposed library.
Project Philosophy
They want beautiful code - in this case they’re talking about DSLs. DSLs are good because they’re concise, readable and maintainable.
Kotlin is great for DSLs because it has:
- Optional semicolons.
- Infix functions.
- No parenthesis for last lambda argument.
- Operator overloading with conventions.
- Extension functions.
- Lambdas with receivers.
- Invoke convention.
Lambdas with receivers
Lambdas that are executed with the context of a receiver.
val isEven: Int.() -> Boolean = {
this % 2 == 0
}
println(234.isEven())
This looks like an extension function, but it’s actually a lamba with receiver.
Server with Ktor
You can use DSLs to specify application behaviour in Kotlin.
A Ktor application has one or more modules. Each module has one or more features which intercept requests/responses. You can use the IntelliJ plugin to design your Ktor server and modules.
The Locations
feature is a step up from manually creating router
and putting get
s there.
Data with Exposed
Exposed is a light-weight SQL library for Kotlin. Operations are specified with DSL and Data Access Objects.
object MyTabe : Table() {
val id = varchar("id", 128).primaryKey()
val userId = varchar("userId", 128) references Users.id
val title = varchar("title", 100)
}
dbTransaction {
Inspirations.insert {
id[id] = UUID.randomUUID().toString()
it[Inspirations.userId] = userId
it[photoUrl] = photo.photoUrl
}
}
dbTransaction
here is a method that calls the lambda on the correct thread in a transaction.
Inspirations.select { Inspirations.id eq id }.mapNotNull {
Inspiration(
id = it[Inspirations.id],
userId = it[Inspirations.userId]
)
}.firstOrNull()
This looks somewhat like SQL.
Frontend with kotlinx.html
Used React as a frontend since JetBrains provides a wrapper for it.
npm install -g create-react-kotlin-app
npx create-react-kotlin-app name
To create a component:
1) Define a properties interface. 2) Define a state interface. 3) Create a component templated on those interfaces. 4) Create a render method (with kotlinx.html DSL). 5) Create a builder.
The power of types
Danny Preussler
Types
Well-typed expressions do not go wrong.
A type is a formal description of the behaviour of a program fragment. (Type systems for programming languages.)
Object oriented languages made typing so painful that so many of us gave up on them.
This caused dynamically typed languages!
Primitive Obsession
Using primitives instead of small objects for simple tasks, eg:
val user: String
val phoneNo: String
const val USER_ADMIN_ROLE = 1
Rule: Don’t put in a name what can be in a type.
suspend fun cutClip(start: Time, duration: Time): ClipId
cutClip(5.sec, 20.sec)
But out of context what does cutClip(5.sec, 20.sec)
mean?
Is the second parameter an end time or a duration?
It should be harder to misuse.
data class PointInTime(val value: Time)
data class TimeDelta(val value: Time)
suspend fun cutClip(start: PointInTime, duration: TimeDelta): ClipId
suspend fun cutClip(start: PointInTime, duration: PointInTime): ClipId
Next example
fun priceInfo(regularPrice: Price): CharSequence
fun discountPriceInfo(discountPrice: Price): CharSequence
The domain has two different types of price - a regular and a discount one.
You should create a RegularPrice
and a DiscountPrice
.
This is Domain Driven Design.
In Soundcloud a URN is a unique identifier.
data class Urn {
val isTrack = ...
val isUser = ...
}
Anytime you find yourself writing code of the form “if object is of Type T1 do this, if oe type T2 do something else” slap yourself. Scott Meyers
sealed class Urn(...) {
data class UserUrn(..): Urn(...) {
val isValid = ...
}
data class CommentUrn ...
data class TrackUrn ...
}
If you pass a boolean into a constructor that’s a code smell - it normally means you’ve got two types of that object.
val urn = UserUrn("")
Why are you able to do this?
We could throw in the constructor - but this moves the error to the runtime.
(And you need to know it can throw an exception.)
We could have a factory method that could return null.
In domain driven design it’s normally called of
and it’s on the companion of the class.
In functional world these are called smart constructors.
Costs
Think of the memory costs and the GC runs.
On JVM allocations happen in Eden space, which is split into the Thread Local Allocation Buffers (no synchronisation). Therefore allocating something is just increasing a pointer. These are very small objects. The compiler might inline your class (escape analysis).
On Android, ART is optimized for allocations (Dalvik will be worse).
Allocations are OK, Types are OK, Yes even enums Jetpack, Google I/O 2018
val a: Int
val b: Int?
b
will be a pointer since it’s nullable but a
will just be an int.
class Age(val age: Int)
val age: Age
… I didn’t catch the point here.
Garbage Collection
The performance of the GC isn’t determined by the number of dead objects, just by the number of live objects (this is the same on Android). (Analyzing Java Memory)
Conclusion
- Types improve readability and documentation.
- Types help compile time correctness (safety, machine checked documentation).
- Types improve abstraction (modularity).
Watch out for “hidden types”:
- Type info in names.
- Type attributes.
- Booleans passed into the constructor.
- Primitives.
Look at the slides to get sources on the performance costs.