You are on page 1of 33

Decoding Kotlin -

Your guide to
solving the
mysterious in
Kotlin

By João Esperancinha 2024/04/24


https://www.meetup.com/dutch-kotlin-user-group/events/300229414/
Topics for today

Nullability Inline and cross-inline


1. Working with the Spring Framework 1. The Java overview
2. Reflection to force nulls

Tail recursive => Tail Cal Optimization (TCO) Data classes


1. What is it 1. Why things work and why things don't
2. Why? work
3. How it makes us work recursively and 2. How to fix the ones that don't
not use mutable 3. How to work with use-site targets.

What does a `delegate` do? and other use-


site targets.
About me
João Esperancinha
● Java
● Kotlin
● Groovy
● Scala
● Software Engineer 10+ years
Kong Champion/Java Professional/Spring Professional
● JESPROTECH owner for 1 year
Kotlin promises a guarantee of null-
safety. Although we can use nullable
members in our classes, we really
shouldn’t whenever possible.

Nullability
Whenever possible?
CRUD Entity Example

CREATE SEQUENCE car_parts_id_sequence


START WITH 1
INCREMENT BY 1
NO MINVALUE @Table(name = "CAR_PARTS")
NO MAXVALUE @Entity
CACHE 1; data class CarPart(
@Id
CREATE TABLE CAR_PARTS ( val id: Long,
id BIGINT NOT NULL DEFAULT val name: String,
nextval('car_parts_id_sequence'::regclass), val productionDate: Instant,
name VARCHAR(100), val expiryDate: Instant,
production_date timestamp, val barCode: Long,
expiry_date timestamp, val cost: BigDecimal
bar_code BIGINT, )
cost float
);
CRUD Entity Example

INSERT INTO CAR_PARTS


(name, production_date, expiry_date, bar_code, cost)
VALUES ('screw', current_date, current_date, 12345, 1.2);
INSERT INTO CAR_PARTS
(name, production_date, expiry_date, bar_code, cost)
VALUES (null, current_date, current_date, 12345, 1.2);

@Test
Is this possible? fun `should mysteriously get a list with a
car part with a name null`() {
carPartDao.findAll()
.filter { it.name == null }
.shouldHaveSize(1)
}
Reflection Example

val carPartDto = CarPartDto(


id = 123L,
Is this possible?
name = "name",
productionDate = Instant.now(),
expiryDate = Instant.now(),
cost = BigDecimal.TEN, data class CarPartDto(
barCode = 1234L val id: Long,
) val name: String,
println(carPartDto) val productionDate:
val field: Field = CarPartDto::class.java Instant,
.getDeclaredField("name") val expiryDate: Instant,
field.isAccessible = true val barCode: Long,
field.set(carPartDto, null) val cost: BigDecimal
println(carPartDto) )
assert(carPartDto.name == null)
println(carPartDto.name == null)
Inline and crossline can be used in
combination with each other. Inline
provides bytecode copies of the code
per each call point and they can
even help avoid type erasure.
Crossinline improves readability
Inline and and some safety, but nothing really
functional.
crossinline.
Why does this matter?
Crossinline as just a marker

fun main() { public final class IsolatedCarPartsExampleKt {


callEngineCrossInline { public static final void main() {
println("Place key in ignition") int $i$f$callEngineCrossInline = false;
int var1 = false;
println("Turn key or press push button ignition") String var2 = "This is the start of the loop.";
println("Clutch to the floor") System.out.println(var2);
println("Set the first gear")
}.run { println(this) } introduction((Function0)IsolatedCarPartsExampleKt$ca
Decompiled
} llEngineCrossInline$1$1.INSTANCE);
var2 = "This is the end of the loop."; code
inline fun callEngineCrossInline(startManually: () -> Unit) { System.out.println(var2);
String var4 = "Engine started!";
run loop@{ System.out.println(var4);
println("This is the start of the loop.") Unit var3 = Unit.INSTANCE;
introduction { int var5 = false;
println("Get computer in the backseat") System.out.println(var3);
return@introduction }
}
public static final void introduction(@NotNull
println("This is the end of the loop.")
} Function0 intro) {
println("Engine started!") Intrinsics.checkNotNullParameter(intro,
} "intro");
LocalDateTime var1 = LocalDateTime.now();
fun introduction(intro: () -> Unit) { System.out.println(var1);
println(LocalDateTime.now()) intro.invoke();
intro() } public final void invoke() {
return String var1 = "Get computer in the
} backseat";
System.out.println(var1);
}
Crossinline as just a marker

fun main() { public final class IsolatedCarPartsExampleKt {


callEngineCrossInline { public static final void main() {
println("Place key in ignition") int $i$f$callEngineCrossInline = false;
int var1 = false;
println("Turn key or press pus button ignition") String var2 = "This is the start of the loop.";
println("Clutch to the floor") System.out.println(var2);
println("Set the first gear")
}.run { println(this) }
introduction((Function0)(new
IsolatedCarPartsExampleKt$main$
Decompiled
} $inlined$callEngineCrossInline$1()));
var2 = "This is the end of the loop."; code
inline fun callEngineCrossInline(crossinline startManually: () - System.out.println(var2);
String var4 = "Engine started!";
> Unit) { System.out.println(var4);
run loop@{ Unit var3 = Unit.INSTANCE;
println("This is the start of the loop.") int var5 = false; public final void invoke() {
introduction { String var1 = "Get computer
System.out.println(var3); in the
println("Get computer in the backseat") } backseat";
startManually() System.out.println(var1);
return@introduction public static final void introduction(@NotNull
Function0 intro) { int var2 = false;
}
Intrinsics.checkNotNullParameter(intro, String var3 = "Place key in ignition";
println("This is the end of the loop.")
} "intro"); System.out.println(var3);
println("Engine started!") LocalDateTime var1 = LocalDateTime.now(); var3 = "Turn key or press push button
} System.out.println(var1); ignition";
intro.invoke(); System.out.println(var3);
fun introduction(intro: () -> Unit) { }
var3 = "Clutch to the floor";
println(LocalDateTime.now()) System.out.println(var3);
intro() var3 = "Set the first gear";
return
System.out.println(var3);
}
}
Crossinline for safety

object SpecialShopNonLocalReturn { object SpecialShopLocalReturn {


inline fun goToStore(crossinline block: () -> Unit) {
inline fun goToStore(chooseItems: () -> Unit) { println("Walks in")
println("Walks in") block()
chooseItems() }
}
@JvmStatic
@JvmStatic fun main(args: Array<String> = emptyArray()) {
fun main(args: Array<String> = emptyArray()) { goToStore {
goToStore { println("Make purchase")
println("Make purchase") return@goToStore
return@main }
} println("Walks out")
println("Never walks out") }
} }
}

@JvmStatic @JvmStatic
public static final void main(@NotNull String[] args) { public static final void main(@NotNull String[] args) {
Intrinsics.checkNotNullParameter(args, "args");
Intrinsics.checkNotNullParameter(args, "args");
SpecialShopLocalReturn this_$iv = INSTANCE;
SpecialShopNonLocalReturn this_$iv = INSTANCE; int $i$f$goToStore = false;
int $i$f$goToStore = false; String var3 = "Walks in";
String var3 = "Walks in"; System.out.println(var3);
System.out.println(var3); int var4 = false;
int var4 = false; String var5 = "Make purchase";
String var5 = "Make purchase"; System.out.println(var5);
String var6 = "Walks out";
System.out.println(var5);

?
System.out.println(var6);
} }

Decompiled
code
Since the late 50’s TCO was already
a theory intentend to be applied to
Tail Recursivity. It allows tail
recursive functions to be
transformed into iterative functions
in the compiled code for better
performance.
Tail Call
Optimization
What is the catch?
Tail Call Optimization

sealed interface Part {


val totalWeight: Double
}

sealed interface ComplexPart : Part{


Why use this?
tailrec fun totalWeight(parts: List<Part>, acc: Double =
val parts: List<Part>
} 0.0): Double {
data class CarPart(val name: String, val weight: if (parts.isEmpty()) {

Tail recursive
Double) : Part {
override val totalWeight: Double
return acc
get() = weight }
} val part = parts.first()
data class ComplexCarPart( val remainingParts = parts.drop(1)
val name: String, val currentWeight = acc + part.totalWeight
val weight: Double, return when (part) {
override val parts: List<Part>
) : is ComplexPart -> totalWeight(remainingParts +
ComplexPart { part.parts, currentWeight)
override val totalWeight: Double
get() = weight
else -> totalWeight(remainingParts, currentWeight)
} }
}
data class Car(
val name: String,
override val parts: List<Part>
) : ComplexPart {

}
override val totalWeight: Double
get() = parts.sumOf { it.totalWeight } All variables
are immutable
Tail Call Optimization

tailrec fun totalWeight(parts: List<Part>, acc: Double =


public static final double totalWeight(@NotNull
0.0): Double { List parts, double acc) {
while(true) {
if (parts.isEmpty()) {
Intrinsics.checkNotNullParameter(parts, "parts");
if (parts.isEmpty()) {
return acc
return acc; }
} val part = parts.first()
val remainingParts = parts.drop(1)

return when (part) {


List remainingParts = CollectionsKt.drop((Iterable)parts,
double currentWeight = acc + part.getTotalWeight();
1);Variables are
val currentWeight = acc + part.totalWeight
Part part = (Part)CollectionsKt.first(parts);

is ComplexPart -> totalWeight(remainingParts +


if (part instanceof ComplexPart) {
part.parts, currentWeight) mutable and
List var10000 = CollectionsKt.plus((Collection)remainingParts,
else -> totalWeight(remainingParts,(Iterable)
currentWeight)
((ComplexPart)part).getParts());
acc = currentWeight;
parts = var10000;
}
}
algorithm is
} else {
acc = currentWeight;
parts = remainingParts;
iterative
}
}
}
Kotlin provides use-site targets that
allow us to specify where particular
annotations have to be applied.
Sometimes we need them and
sometimes we don’t

Data classes and


Why?
Frameworks
Working with Data classes

Doesn’t work Works!


Why
@Table(name = "CAR_PARTS")
@Entity
data class CarPart(
use-site @Table(name = "CAR_PARTS")
@Entity
data class CarPart(
@Id
val id: Long,
targets? @Id
val id: Long,
@Column @Column
@NotNull @field:NotNull
@Size(min=3, max=20) @field:Size(min=3, max=20)
val name: String, val name: String,
val productionDate: Instant, val productionDate: Instant,
val expiryDate: Instant, val expiryDate: Instant,
val barCode: Long, val barCode: Long,
@Min(value = 5) @field:Min(value = 5)
val cost: BigDecimal val cost: BigDecimal
) )
Working with Data classes

https://kotlinlang.org/docs/annotations.html#annotation-use-site-targets

If you don't specify a use-site target, the target is chosen


according to the @Target annotation of the annotation being
used. If there are multiple applicable targets, the first applicable
target from the following list is used:
● param
● property
● field
Working with Data classes

@Target({ElementType.METHOD,
ElementType.FIELD,
ElementType.ANNOTATION_TYPE,
ElementType.CONSTRUCTOR,
ElementType.PARAMETER,
ElementType.TYPE_USE})
PARAMETE
@Retention(RetentionPolicy.RUNTIME) R
@Repeatable(List.class)
@Documented is
@Constraint(
validatedBy = {} selected
)
public @interface Size {
Working with Data classes

public final class CarPart {


@Id
@NotNull
public final Instant getProductionDate() {
Not where we want
private final long id; return this.productionDate; them to be,
@Column
@NotNull
}
@NotNull
but where they are
private final String name; public final Instant getExpiryDate() { expected
@NotNull return this.expiryDate;
private final Instant productionDate; }
@NotNull public final long getBarCode() {
private final Instant expiryDate; return this.barCode;
private final long barCode; }
@NotNull @NotNull
private final BigDecimal cost; public final BigDecimal getCost() {
public final long getId() { return this.cost;
return this.id; }
public CarPart(long id, @jakarta.validation.constraints.NotNull @Size(min =
} 3,max = 20) @NotNull String name, @NotNull Instant productionDate, @NotNull
@NotNull Instant expiryDate, long barCode, @Min(5L) @NotNull BigDecimal cost) {
public final String getName() { Intrinsics.checkNotNullParameter(name, "name");
return this.name; Intrinsics.checkNotNullParameter(productionDate, "productionDate");
} Intrinsics.checkNotNullParameter(expiryDate, "expiryDate");
Intrinsics.checkNotNullParameter(cost, "cost");
Working with Data classes

public final class CarPart {


@Id
private final long id;
@Table(name = "CAR_PARTS")
@Entity @Column
data class CarPart( @NotNull
@Id @Size(
val id: Long, min = 3,
@Column max = 20
@field:NotNull )
Since @field forces
@field:Size(min=3, @org.jetbrains.annotations.NotNull
max=20) the target, these
val name: String, private final String name;
val productionDate:@org.jetbrains.annotations.NotNull
Instant,
annotations get
val expiryDate: Instant,
private final Instant productionDate; applied where they
val barCode: Long, @org.jetbrains.annotations.NotNull
@field:Min(value = private
5)
should
final Instant expiryDate;
val cost: BigDecimal
private final long barCode;
)
@Min(5L)
@org.jetbrains.annotations.NotNull
private final BigDecimal cost;
Delegation is a great part of the
Kotlin language and it is quite
different than what we are used to
seeing in Java

Delegates and other But how can we use it?


use-site targets
Working with Delegates

interface Horn { annotation class DelegateToWagonHorn


fun beep()
} annotation class DelegateToCarHorn

class CarHorn : Horn {


override fun beep() { Where is this
class SoundDelegate(private val initialHorn: Horn)being
{ applied to?
println("beep!") operator fun getValue(thisRef: Any?, property: KProperty<*>): Horn {
return initialHorn
Horn or SoundDelegate?
}
} }
}
class WagonHorn : Horn {
override fun beep() {
class HornPack {
println("bwooooooo!")
@delegate:DelegateToWagonHorn
}
val wagonHorn: Horn by SoundDelegate(WagonHorn())
}

@delegate:DelegateToCarHorn
val carHorn: Horn by SoundDelegate(CarHorn())
}
Working with Delegates

public final class HornPack {


// $FF: synthetic field
static final KProperty[] $$delegatedProperties = new KProperty[]{Reflection.property1(new
PropertyReference1Impl(HornPack.class, "wagonHorn", "getWagonHorn()Lorg/jesperancinha/talks/carparts/Horn;",
0)), Reflection.property1(new PropertyReference1Impl(HornPack.class, "carHorn",
"getCarHorn()Lorg/jesperancinha/talks/carparts/Horn;", 0))};
@DelegateToWagonHorn
@NotNull
private final SoundDelegate wagonHorn$delegate = new SoundDelegate((Horn)(new WagonHorn()));
@DelegateToCarHorn
@NotNull
SoundDelegate
private final SoundDelegate carHorn$delegate = new SoundDelegate((Horn)(new CarHorn()));

@NotNull
public final Horn getWagonHorn() {
return this.wagonHorn$delegate.getValue(this, $$delegatedProperties[0]);
}

@NotNull
public final Horn getCarHorn() {
return this.carHorn$delegate.getValue(this, $$delegatedProperties[1]); No Horn!
}
}
Working with Delegates

class SanitizedName(var name: String?) {


operator fun getValue(thisRef: Any?,
property: KProperty<*>): String? = name public final class PartNameDto {
static final KProperty[] $$delegatedProperties = new KProperty[]{Reflection.mutableProperty1(new
operator fun setValue(thisRef: Any?,
MutablePropertyReference1Impl(PartNameDto.class, "name", "getName()Ljava/lang/String;", 0))};
property: KProperty<*>, v: String?) {
@Nullable
name = v?.trim() private final SanitizedName name$delegate = new SanitizedName((String)null);
} @NotBlank
} @Size(
max = 12
)
@Nullable
class PartNameDto {
public final String getName() {
@get:NotBlank
return this.name$delegate.getValue(this, $$delegatedProperties[0]);
@get:Size(max = 12) }
var name: String? by SanitizedName(null) …
override fun toString(): String {
return name ?: "N/A" public final class ImpossiblePartNameDto {
} static final KProperty[] $$delegatedProperties = new KProperty[]{Reflection.mutableProperty1(new
MutablePropertyReference1Impl(ImpossiblePartNameDto.class, "name", "getName()Ljava/lang/String;",
}
0))};
@NotBlank
class ImpossiblePartNameDto { @Size(
@delegate:NotBlank max = 12
@delegate:Size(max = 12) )
var name: String? by SanitizedName(null) @Nullable
override fun toString(): String { private final SanitizedName name$delegate = new SanitizedName((String)null);
@Nullable
return name ?: "N/A"
public final String getName() {
}
return this.name$delegate.getValue(this, $$delegatedProperties[0]);
} }
Working with Delegates

@Service
data class DelegationService(
val id: UUID = UUID.randomUUID()
) {
@delegate:LocalDateTimeValidatorConstraint
@get: Past
val currentDate: LocalDateTime by LocalDateTimeDelegate()
}

public class DelegationService {


// $FF: synthetic field
static final KProperty[] $$delegatedProperties = new KProperty[]{Reflection.property1(new
PropertyReference1Impl(DelegationService.class, "currentDate",
"getCurrentDate()Ljava/time/LocalDateTime;", 0))};
@LocalDateTimeValidatorConstraint
@NotNull
private final LocalDateTimeDelegate currentDate$delegate;
@NotNull
private final UUID id;

@Past
@NotNull
public LocalDateTime getCurrentDate() {
return this.currentDate$delegate.getValue(this, $$delegatedProperties[0]);
}
What’s next?
➔ Better understanding of the Kotlin Language.

➔ Don’t fight the Spring Framework or anything else like Quarkus. They are not evil and they are not magic.

➔ Read the Kotlin documentation and only use Google as a last resort.

➔ Nothing is perfect and Kotlin also falls into that category and recognizing that, allow us to be better.
Thank you!
Questions?
Resources
● Null Safety: https://kotlinlang.org/docs/null-safety.html
● Inline: https://kotlinlang.org/docs/inline-functions.html
● Tail Call Optimization: https://kotlinlang.org/docs/functions.html#tail-recursive-functions
● Annotation use-site targets: https://kotlinlang.org/docs/annotations.html#annotation-use-site-targets
● Spring Validation via AOP : https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-validation.html
Source code and slides
● https://github.com/jesperancinha/kotlin-mysteries

● Scribd: https://www.scribd.com/presentation/726367924/Decoding-Kotlin-Your-Guide-to-Solving-the-Mysterious-in-Kotlin
● Slide-share: https://www.slideshare.net/slideshow/decoding-kotlin-your-guide-to-solving-the-mysterious-in-kotlinpptx/267506251
About me
● Homepage - https://joaofilipesabinoesperancinha.nl
● LinkedIn - https://www.linkedin.com/in/joaoesperancinha/
● YouTube - JESPROTECH
■ https://www.youtube.com/channel/UCzS_JK7QsZ7ZH-zTc5kBX_g
■ https://www.youtube.com/@jesprotech
● X - https://twitter.com/joaofse
● GitHub - https://github.com/jesperancinha
● Hackernoon - https://hackernoon.com/u/jesperancinha
● DevTO - https://dev.to/jofisaes
● Medium - https://medium.com/@jofisaes
See you
Next time!
See you
Next time!

You might also like