Blogg

Här finns tekniska artiklar, presentationer och nyheter om arkitektur och systemutveckling. Håll dig uppdaterad, följ oss på Twitter

Callista medarbetare Jesper Holmberg Callista medarbetare Magnus Ekstrand Callista medarbetare Marcus Cedergren

Callista på KotlinConf

// Jesper HolmbergMagnus EkstrandMarcus Cedergren

Några Callista-konsulter var nyligen på KotlinConf i Amsterdam.

kotlin-conf.jpg
Mingel på KotlinConf 2023. Foto: Hadi Hariri

KotlinConf har gått av stapeln för första gången på fyra år, denna gång i Amsterdam. Konferensen samlade ca 1300 utvecklare. En grupp från Callista närvarade vid konferensen, och vi kan konstatera att Kotlin-världen är vid god vigör och växer på alla bredder: antalet utvecklare som använder Kotlin växer snabbt, antalet teknikdomäner där Kotlin kan användas växer minst lika snabbt, och språket i sig självt blir allt mer kraftfullt. Ett tecken på att Kotlin numera är väl etablerat är det faktum att Google, väl känt för den begränsade mängd språk som utvecklare internt kan använda sig av, numera har tagit upp Kotlin på listan över dessa språk även för backend-utveckling.

Att språket Kotlin växer snabbt är en sanning med modifikation, eftersom det har förändrats ganska måttligt de senaste åren. Detta förklaras dock av att Kotlins skapare har arbetat hårt på en ny version av Kotlin-kompilatorn, som både ska ge bättre prestanda och vara en stabil grund för vidareutveckling av språket. Den nya kompilatorn är snart färdig. Planen är att den släpps i höst och i en release som kommer att vara Kotlin 2.0. Det som framförallt har förändrats i den nya kompilatorn är dess frontend, d.v.s. den del som analyserar och tolkar användarens kod, vilket för med sig att alla verktyg som bygger på en förståelse av koden, som t.ex. IDE:ns varningar och förbättringsförslag, kommer att bli kvickare. Med andra ord kan vi se fram emot en bättre upplevelse när vi utvecklar Kotlin framgent.

Som nämnts ovan kommer ett antal nya språk-finesser att introduceras i Kotlin efter att den nya kompilatorn är släppt, som t.ex. “Collection literals”, “Name-based destructuring” och inte minst “Context receivers”. För en beskrivning av dessa spännande nyheter kan man ta del av Roman Elizarovs genomgång från konferensens Keynote. Senare i vår kommer många av konferensens presentationer att kunna hittas på Youtube.

Kotlin på många plattformar

Det var länge sedan Kotlin var bundet till bara JVM:en, och idag finns en uppsjö av plattformar där Kotlin kan köras, mer eller mindre experimentellt: JVM/Backend, Android, iOS, node.js, och även en kommande WebAssembly-version. Många presentationer handlade om dessa nya möjligheter, och det ska bli intressant att se hur väl Kotlin lyckas etablera sig utanför sitt ursprung i Android/JVM.

Arrow för funktionell programmering

En av de trevligaste bekantskaperna under konferensen för oss Callista-konsulter var biblioteket Arrow, som funnits i många år men skiftat skepnad den senaste tiden. Arrow är en samling lösningar för att skriva funktionell kod i Kotlin. Arrow är på väg mot en avgörande 2.0-version där fokus ligger på att hålla biblioteket minimalt och med ett starkt fokus på att följa Kotlins idiom. Arrow och andra liknande verktyg har tidigare ofta lidit av en vilja att göra för mycket, och mer specifikt få Kotlin att likna mer renodlat funktionella språk som Haskell och Scala. Denna ambition har ofta lett till otympliga verktyg som försökt implementera funktionalitet som bäst lämpar sig på språk-nivå i klumpiga biblioteksfunktioner.

Arrow 2.0 gör upp med denna historik och fokuserar istället på att ge utvecklare en samling minimala men kraftiga verktyg från den funktionella världen, som typsäker felhantering, immutable state-hantering, coroutine-utilities etc, där alla verktyg är designade för att passa in i idiomatisk Kotlin istället för att försöka imitera funktionalitet som finns i andra språk.

Kotlin Dataframe

Andrew Goldberg hade en intressant introduktion till biblioteket Kotlin Dataframe som ingår i ett ekosystem av verktyg riktade främst för data science.

En Dataframe är en 2-dimensionell tabell-liknande datastruktur med rader och kolumner, där varje kolumn innehåller data av samma typ. Det är inspirerat av liknande lösningar som finns i andra programmeringsspråk som tex Python Pandas. Kotlins statiska typning och funktionella inslag ger flera fördelar - tex är det snabbare än Pandas särskilt när det gäller stora dataset. Utifrån det som Andrew presenterade verkar det vara lätt att använda och har en enkel och intuitiv syntax som gör det enkelt att hantera även komplexa datamängder. Det stöder en rad olika dataformat som CSV, Excel, JSON och SQL-databaser. Det är också användbart för att skapa rapporter och visualiseringar av data på ett enkelt och effektivt sätt - även om man inte primärt arbetar med data science eller maskininlärning.

Med en Kotlin Dataframe-plugin i Jupyter Notebooks visade han hur smidigt man stegvis och “explorativt” kan bygga en fungerande kedja med inläsning, tvättning, konvertering och till sist aggregering, analys och presentation av data från olika datakällor. Allt med full typsäkerhet och Kotlins alla funktionella godbitar för att hantera collections. När man labbat klart har man färdig Kotlinkod som i princip direkt kan integreras i verkliga use cases. Definitivt något att testa nästa gång man skall göra en one-off processning av någon sorts strukturerad datakälla.

kotlin-dataframe.png
Kotlin dataframe kodexempel i Jupyter Notebook . Källa: https://blog.jetbrains.com/kotlin/2022/06/kotlin-dataframe-library-preview/

Context receivers

Kotlin stödjer sedan release 1.6.20 en konstruktion som kallas context receivers. Denna feature är fortfarande markerad som experimentell, men beräknas släppas i slutgiltig version i någon av Kotlins 2.x-releaser. Context receivers har tagits fram i syfte att göra det enklare att tillföra en kontext till funktioner och klasser.

Idag går det att tillföra kontext till Kotlin-funktioner via extension functions.

fun String.escapeForXml() : String {
    ...
}

val escaped = input.escapeForXml()

Att använda en extension function är enkelt, men hur anropar vi en extension function som definieras som en del av en annan klass? Det är när vi behöver tillföra ytterligare kontext och scope som det börjar bli krångligt och det är då context receivers blir användbart. Context receivers låter oss lägga till kontext i koden där kontexten behövs istället för som fram till nu, behöva skicka kontexten explicit till de delar i koden som behöver den.

Men hur använder vi det?

I exemplet nedan har vi ett UserRepository med en funktion för att returnera en användare

data class User(val id: UserId, val name: String)

interface UserRepository {
  fun getUserById(id: UserId): User?
}

Då kan vi tillgängligöra respositoryt i en annan funktion på en annan plats i koden genom att ge funktionen ett kontext.

context(UserRepository)
fun getUserName(id: UserId): String? =
  getUserById(id)?.name

Vid ett anrop till en funktion med en context receiver så förväntas det att det finns en implicit mottagare. Det går alltså inte att anropa en sådan funktion via objektet.

fun main() {
   UserRepository().getUserName(UserId(1)) // ERROR
}

Det går att deklarera flera context receivers (till skillnad från extension functions som endast kan ha en). På så vis skulle det gå att tillföra kontext i form av loggning, transaktionshantering eller som i exemplet nedan, felhantering.

context(Raise<NoSuchUserError>, UserRepository)
fun getUserName(id: UserId): String? =
  getUserById(id)?.name

Ett populärt Android-exempel är att beräkna dp-storlek (density-independent pixels). Problemet är att dp-storleken beror på en vy som har en visst antal pixlar per tum. Lösningen är att ange en (extension) property “dp” som skulle kunna ha en context receiver av typen View.

context(View)
val Float.dp get() = this * resources.displayMetrics.density

context(View)
val Int.dp get() = this.toFloat().dp

Extension functions har flera begränsningar:

  • extension functions är begränsade till ett enda kontext
  • en extension function kan endast anropas via klassen där den är deklarerad
  • ge kontext till en extension function via klassen där den är deklarerad kan göra det svårt att förstå hur funktionen ska anropas och vad syftet är med funktionen vilket exemplet nedan visar.
interface LoggingContext {
    val logger: Logger
}

fun LoggingContext.sendNotification(notification: NotificationData) {
    logger.info("Sending notification $notification")
    notificationSender.send(notification)
}

// sendNotification ser ut att vara en metod i LoggerContext vilken den inte är
// att skicka en notifikation har inget med loggning att göra

Context receivers löser dessa problem på ett smidigt och praktiskt sätt. För att få en bra förståelse för vad context receivers är kan man läsa om förslaget här.

Slutord

Som avslutning kan vi konstatera att KotlinConf var både välorganiserad och inspirerande, och vi ser fram emot att åka på fler upplagor under kommande år.

Tack för att du läser Callistas blogg.
Hjälp oss att nå ut med information genom att dela nyheter och artiklar i ditt nätverk.

Kommentarer