Visualizzazione post con etichetta first class functions. Mostra tutti i post
Visualizzazione post con etichetta first class functions. Mostra tutti i post

martedì 20 agosto 2013

Funzioni "First Class" e "Higher Order" nella Programmazione Funzionale



In un recente post ho parlato della Programmazione Funzionale (FP) come di un paradigma di programmazione per computer che è sostanzialmente esente da side-effect, cioè a dirla in modo un po' brutale: è esente da comportamenti anomali a run-time.


In pratica, un programma scritto in FP pura si comporta come un foglio Excel. Si è mai visto un foglio Excel avere comportamenti anomali? Ci sono dei valori in input, ci sono delle formule e tutto quello che si ottiene sono dei valori di output che derivano dall'applicazione di quelle formule.

In modo simile, un programma FP è una funzione che prende come input una funzione - quindi, si potrebbe dire: una formula - e dà in output un certo risultato. A complicare le cose, c'è il fatto che una funzione FP solitamente prende in input una funzione che a sua volta prende in input una funzione e così via, fin quando ce ne sia bisogno per realizzare lo scopo del programma.

Per indicare questo tipo di comporamento, si dice che un generico linguaggio FP supporta le First Class Functions.

Una funzione, per essere "First Class", deve avere questa caratteristica: può essere passata ad un'altra funzione come qualsiasi altro valore.

Facciamo un esempio. (L'esempio in basso è scritto in linguaggio Scala, che è un linguaggio di programmazione che supporta molto bene il paradigma funzionale).


val areaDelCerchio = (raggio: Double) => { 
    raggio*raggio*Math.PI 
}
    
def printResult(result: Any) {
    println("L'area del cerchio e' : " + result.toString)
}
    
printResult( areaDelCerchio(10.0) )


La formula dell'area del cerchio viene memorizzata come valore (val). Questo basta per far sì che Scala possa dirsi un linguaggio dove le funzioni sono First Class, cioè vengono trattate come valori di prima classe, come lo sono solitamente gli interi o le stringhe. Significa che qualunque funzione può prendere come parametro un valore di prima classe: quindi, tipi elementari, oggetti e funzioni.

La funzione printResult è una funzione che si dice di ordine superiore, Higher Order Function, perché può prendere come parametro un qualsiasi valore e - anche! - una funzione.

Da un punto di vista di controllo dei tipi, la funzione/valore areaDelCerchio prende come parametro un Double (raggio, un valore decimale a doppia precisione) e ritorna un Double.

Qualsiasi oggetto in Scala - e quindi un Double, o una First Class Function - può essere convertito in Stringa. Ed ecco che printResult prende come parametro Any, cioè qualsiasi tipo o funzione, che possa essere rappresentato come stringa - che possegga il metodo .toString.

Poichè la funzione areaDelCerchio non mantiene variabili di stato e non accetta input dall'esterno che potrebbero modificarla, il suo comportamento è predicibile a priori ed esente da side-effect.

Una funzione Higher Order che prende come input tipi semplici o funzioni o funzioni di funzioni, ha un comportamento sempre predicibile e, una volta compilata, sarà esente da bug a run-time. Detto in altri termini: se un programma funzionale puro si compila - quindi non dà errori a compile-time - sarà esente da bug.

La capacità di esprimere un programma per computer, anche complesso, in termini di Higher Order Functions è dunque una garanzia che lo stesso, se si compila, avrà un comportamento predicibile e senza malfunzionamenti.

Se si pensa alle ingenti risorse che vengono impiegate normalmente in informatica per il test e il controllo della qualità del software, le implicazioni economiche di un simile approccio alla programmazione sono del tutto evidenti.


giovedì 28 febbraio 2013

First Class Functions in Scala


A volte è necessario passare come argomenti dei metodi o delle funzioni non un parametro normale come un intero o una stringa, ma un'altra funzione. Ad esempio vorremmo scrivere un metodo che applica una somma o una moltiplicazione di due numeri, e vogliamo passargli come argomento la funzione "somma" o la funzione "moltiplicazione".

Un esempio meno banale: vorremmo scrivere una funzione di sorting che prende in input la collezione di oggetti da ordinare e l'algoritmo di sort. Questa funzione avrebbe quindi due parametri: la collezione e una funzione con l'implementazione dello specifico algoritmo.

Dunque vorremmo costruire una procedura che esegue una determinata operazione sui suoi parametri, e vorremmo passare come parametro l'operazione stessa, in modo da rendere la procedura del tutto generica, e perciò anche riutilizzabile.

Normalmente, nella programmazione orientata agli oggetti, questo si ottiene con un pattern che si chiama "Functor". Il Functor è un'interfaccia che espone un metodo che includerà la nostra funzione. Sta poi alla particolare implementazione dell'interfaccia stabilire se quella funzione è, ad esempio, una moltiplicazione o una somma.

Posto che in Scala le interfacce non esistono, ma esistono i "trait", di cui ho parlato in un altro post, si potrebbe pensare di scrivere due classi, Somma e Moltiplicazione, così fatte:

trait Functor {
  def function(x: Int, y: Int) : Int
}

class Somma extends Functor {
  def function(x: Int, y: Int) : Int = (x+y)
}

class Moltiplicazione extends Functor {
  def function(x: Int, y: Int) : Int = (x*y)
}

Cosa sono le "first class functions"? Sono una particolarità del linguaggio che ci permette di passare, come argomento di una funzione o di un metodo di classe, una funzione; esattamente quello che otteniamo con il pattern "Functor".

In questo caso però, noi non passiamo un oggetto che ha come metodo una specifica funzione: noi passiamo direttamente la funzione!

Questa è la sintassi da usare:

object FirstClassFunctions {
  val somma = (x: Int, y: Int) => (x+y)
  val moltiplicazione = (x: Int, y: Int) => (x*y)
}


La variabile "somma" non è né un tipo primitivo (intero o stringa, ad esempio), nè un oggetto. E' una funzione. La prima parte dice quali sono i tipi che la funzione manipola, e la seconda parte è la funzione vera e propria. Somma è una funzione che somma il valore x al valore y. Similmente per moltiplicazione.


object Main {

  // Functor
  val functSomma : Functor = new Somma()
  val functMoltiplicazione : Functor = new Moltiplicazione()

  def testFunctor(f:Functor, x:Int, y:Int) {
    println("Il risultato e' = " + f.function(x,y))
  }

  // First Class
  def testFirstClass(f: (Int,Int) => Int, x:Int, y:Int)  {
    println("Il risultato e' = %d".format(f(x,y)))
  }


  // Main
  def main(args: Array[String]) {

    testFunctor(functSomma, 6, 5)
    testFunctor(functMoltiplicazione, 6, 5)

    testFirstClass(FirstClassFunctions.somma, 6, 5)
    testFirstClass(FirstClassFunctions.moltiplicazione, 6, 5)

  }

}


Il codice di sopra richiama le funzioni sia sotto forma di Functor, che sotto forma di First Class Functions.
Com'è ovvio, la possibilità di passare direttamente funzioni come parametri di metodi, piuttosto che inventare una tassonomia apposita di classi, riduce la complessità del codice, aumenta la leggibilità e quindi in definitiva migliora la qualità del codice sorgente.