Una delle caratteristiche che adoro di Kotlin è la sua espressività. Alcuni degli idiomi di questo linguaggio permettono di ottenere “codice parlante”: espressioni davvero molto simili a frasi in plain english.

Tra le tante peculiarità che fanno di Kotlin uno dei linguaggi più apprezzati da chi sviluppa domain-specific language c’è anche la notazione infix.

Ne parliamo in questo post.

Funzioni o operatori?

Lavorando con oggetti di tipo Map, in Kotlin, vi sarà capitato sicuramente di utilizzare un’espressione di questo tipo per dichiarare una nuova mappa:

val map = mapOf(
    1 to "uno",
    2 to "due",
    3 to "tre"
)

Come vedete, stiamo utilizzato un to per associare una chiave ad un valore, in modo molto immediato ed intuitivo. Ma to, esattamente cos’è?

Un operatore? Una keyword, o una funzione?

Benché, a tutti gli effetti, il to appaia come un costrutto proprio del linguaggio, in realtà si tratta di una semplice funzione; una funzione dichiarata in modo simile:

infix fun Any.to(other: Any) = Pair(this, other)

La parola chiave infix, nella dichiarazione di una funzione, consente d’invocare la stessa senza i classici punto e parentesi: semplicemente interponendo il nome della funzione tra il ricevente e il parametro.

Ovviamente nulla vieta di effettuare la chiamata nel modo classico. Nel caso di to, potremmo sempre utilizzarla così:

val map = mapOf(
    1.to("uno"),
    2.to("due"),
    3.to("tre")
)

Requisiti

Abbiamo quindi visto che il prefisso infix è in grado di trasformare una semplice funzione in qualcosa di magico; ma per poter essere invocata tramite la notazione infix una funzione deve rispettare anche una serie di requisiti:

  • dev’essere una funzione membro o una funzione di estensione;
  • deve definire un singolo parametro;
  • il parametro non deve essere un vararg né prevedere un valore di default.

Infine, un altro vincolo da rispettare riguarda l’invocazione di queste funzioni. La notazione infix richiede infatti che vengano sempre esplicitati sia il receiver (oggetto target), sia il parametro della funzione.

Riepilogando quindi: non è possibile definire funzioni infix al di fuori di classi o senza un’associazione ad un receiver type.

Le seguente definizioni non sono valide:

infix fun invalidFun1(vararg p: String) { 
    // niente vararg!
} 

infix fun invalidFun2(p1: String, p2: String) { 
    // è ammesso un solo parametro!
} 

infix fun invalidFun3(p: String = "hello") { 
    // ... singolo parametro, ma senza valore di default!
} 

è invece valida la seguente funzione:

infix fun String.extInfixFun(x: String)

come anche:

class MyClass {
    infix fun memberInfixFun(m: Int) : String {
        // ...
    }

    fun memberFun(){
        // da invocare così
        this memberInfixFun  3
        
        // oppure così
        memberInfixFun(3)
    }
}

In conclusione

La notazione infix è una di quelle caratteristiche di Kotlin che permettono di scrivere codice in maniera davvero naturale. La leggibilità e l’espressività che ne derivano apportano un vantaggio sicuramente rilevante in termini di produttività e manutenibilità della codebase.

La prossima volta che vi capita di trovare nel codice quella che sembra una keyword sconosciuta, andate a sbirciare meglio: magari è semplicemente una funzione… 😉

A presto,

David