Kotlin Language

Funzioni

Capitolo importante per ogni linguaggio moderno, Kotlin ha un pieno supporto verso le funzioni che si presentano come un argomento ricco e complesso. la keyword che le introduce, come è evidente da altri esempi ed in particolare dalla sempre presente funzione "main", è fun.
Il formato è il seguente:

fun nomefunzione (parametri opzionali) : tipo di ritorno
{
corpo della funzione
}

Subito andiamo con un esempio

  Esempio 3.1
1
2
3
4
5
6
7
8
9
10
11
12
fun doppio(x : Int) : Int
{
  return x * 2
}

fun main(args: Array<String>): Unit
{
  print("Inserisci un valore: ")
  val s1 = readLine()
  val x1 = s1!!.toInt()
  println("Valore doppio: "+ doppio(x1))
}

Banalmente il programma chiede in input un valore e lo restituisce raddoppiato. Per evitiamo di farci troppe domande (ad esempio cosa sono quei !! alla riga 10), La funzione definita alla riga 1 prende in ingresso un parametro di tipo intero (vvoiamente la chiamata alla funzione stessa deve passare un intero, come avviene alla riga 11) e restituisce un intero tramite l'istruzione return. In questo caso abbiamo visto un valore di ritorno. Ma se la nostra funzione non ne prevedesse? Come mostrato alla riga 6 avremmo il tipo di ritorno Unit, che, ovviamente, non si applica solo al main. Unit si può spiegare in vari modi, diciamo semplicemente che questo tipo, come spiegato anche sul sito ufficiale, ha un solo valore, UNIT.Value che non deve essere esplicitamente restituito. Comunque esso è diverso da null, per non cadere nell.equivoco.

  Esempio 3.2
1
2
3
4
5
6
7
8
9
10
fun saluto() : Unit
{
println("Ciao!")
}

fun main(args: Array<String>): Unit
{
println("Ora ti saluto")
saluto()
}

La funzione definita alla riga 1 non ha nè un parametro in ingresso nè un valore di ritorno.
Proseguendo secondo l'iter suggerito da Breslav e soci, è interessante notare come Kotlin accetti le funzioni innestate (sul sito le chiamano local functions) ovvero funzioni definite in altre funzioni e alle quali possiamo passare dei parametri ricevendone normalmente dei valori di ritorno:

  Esempio 3.3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun saluto() : Unit
{
  fun stringa(s1: String) : String
  {
    return s1 + s1
  }
println(stringa("ciao"))
}

fun main(args: Array<String>): Unit
{
println("Ora ti saluto")
saluto()
}

La funzione alla riga 3 è innestata in quella definita alla 1. Una funzione "interna" è in grado di vedere valori al suo esterno ed anche di ricevere valori restituiti dalla funzione esterna usando un particolare tipo di valore restituito che vedremo in altro paragrafo. Per verificare la veridicità della prima parte di quanto detto possiamo ad esempio riscrivere la funzione "saluto" come segue:

fun saluto() : Unit
{
  val s2: String = "aaa"
  fun stringa(s1:String) : String
    {
    return s1 + s2
    }
  println(stringa("ciao"))
}


Le peculiarità del trattamento dell funzioni da parte di Kotlin proseguono con i defaulted parameters, ovvero parametri ai quali sono assegnati valori di default. Si tratta di una feature già disponibile in altri linguaggi tra i quali ad esempio Scala o lo stesso Ceylon. Vediamo un esempio:

  Esempio 3.4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun trenumeri(x:Int = 1, y:Int = 2, z: Int = 3) : Int
{
  var somma: Int = 0
  somma = x + y + z
  return somma
}

fun main(args: Array<String>): Unit
{
  println(trenumeri(4,5,6))
  println(trenumeri(4,5))
  println(trenumeri(4))
  println(trenumeri())
}

La funzione "trenumeri" ha 3 parametri che, come si vede alla riga 1, sono inizializzati con dei valori di deafult. In questo modo, è possibile effettuare la chiamata a tale funzione nei modi evidenziati alle righe 10,11,12 e 13, ovvero fornendo tutti e 3 i parametri che sovrascrivono quelli di default, oppure fornendone solo due (in questo caso posizionalmente vengono sovrascritti i primi 2) oppure uno (viene sovrascritto il primo) oppure nessuno (si usano i valori di deafult ed il risultato pertanto è 1+2+3 = 6). E' da notare che i parametri vengono sempre passati per posizione. Quindi se per esempio avessimo inzializzato la nostra funzione come segue:

fun trenumeri(x:Int = 1, y:Int, z: Int = 3) : Int

ovvero senza fornire un valore di default al secondo parametro le chiamate alle righe 10 e 11 sarebbero risultate corrette ma quelle alla 12 ed alla 13 no. In particolare quella alla riga 12 avrebbe assegnato il valore 4 al parametro x e non a y anche se x ha un valore di default e y no.

Un'altra feature interessante è quella costituita dai named parameters, cioè, allo stato pratico, la possibilità di chiamare i parametri attraverso il nome ad essi attribuito. Anche questa è una caratteristica che Scala, il linguaggio più simile a Kotlin, possiede. Usando i parametri attraverso il loro nome è anche possibile risolvere il problema accennato qui sopra, ovvero attribuire un valore specificatamente al parametro che vogliamo. L'esempio 3.4 modificato come segue chiarisce il concetto:

  Esempio 3.5
1
2
3
4
5
6
7
8
9
10
11
12
13
fun trenumeri(x:Int = 3, y:Int, z: Int = 9) : Int
{
var somma: Int = 0
somma = x + y + z
return somma
}

fun main(args: Array<String>): Unit
{
println(trenumeri(4,5,6))
println(trenumeri(4,5))
println(trenumeri(y = 4))
}

La riga 13 chiama la funzione passando un solo parametro ma lo attribuisce esattamente al parametro y.

Le funzioni in Kotlin sono in grado di accettare anche altre funzioni come parametri e di restituire funzioni come valori di ritorno (high order functions). In attesa di chiarire le cose presento un esempio sintatticamente valido ma di pochissimo aiuto pratico, anche perchè ci sono alcuni argomenti da chiarire:

  Esempio 3.6
1
2
3
4
5
6
7
8
9
10
fun ripeti(z: () -> Unit ) : Unit
{
println("struttura di z: ")
println(z)
}

fun main(args: Array<String>): Unit
{
ripeti({() -> println("")})
}

Prendetelo giusto come primo assaggio.

La funzioni permettono anche di restituire più valori in caso di necessità.
NOTA: L'esempio preparato si basava sui cosidetti "tuples" che ora sono deprecati. Attendo l'evoluzione del linguaggio per chiarire la cosa.


Tra le altre particolarità che vedremo approfondendo il discorso, la possibilità per le funzioni di avere parametri generici, i parametri marcati "vararg" ecc. Ne parleremo.
Per il momento direi che c'è già abbastanza materiale per poter lavorare un po'.