martedì 29 aprile 2014

Di che tipo è una Funzione Lambda?


Le funzioni lambda sono balzate agli onori delle cronache soprattutto perché l'ultima versione di Java le supporta direttamente, mentre sono molti altri i linguaggi che le integrano nella loro sintassi, ad esempio Ruby, Python e Scala.

Nel campo della programmazione una funzione lambda è una funzione anonima che può essere chiamata e passata direttamente, senza cioè essere nominata con un identificatore, come lo è una funzione normale, che ha un suo nome che rende possibile chiamarla nominalmente.

Ad esempio, in Scala, una funzione lambda ha questa sintassi:



val circleArea = (radius: Double) => 
                     { radius * radius * Math.PI }



in questo caso il nome "circleArea" non è il nome della funzione, ma è il nome dell'oggetto a cui si associa la funzione lambda. La differenza è sottile ma sostanziale: una funzione lambda può essere associata a molte diverse variabili con diverso nome, mentre una funzione normale ha uno e un solo nome.

Perché mai un programmatore dovrebbe usare funzioni senza nome? Il motivo principale risiede nell'immediatezza: sono parti di codice di facile scrittura, tipicamente formule matematiche, il cui uso è in porzioni di codice "vicine" a quello in cui vengono definite. Oppure ancora si tratta di shortcut, cioè di un modo veloce per passare mini-funzioni ad una super-funzione che le richiama all'interno di se stessa - il caso, nell'esempio sotto in Java8, della gestione degli eventi nei controlli:

// Lambda in Java 8
button.addActionListener( (e) -> {
        System.out.println("The button was clicked. From lambda expressions !");
});


C'è però una questione: quando passo una funzione anomima ad un'altra funzione, qual è il "tipo" con cui la identifico? In altre parole: se scrivo un metodo che prende come input una funzione lambda, come sarà la "firma" di quel metodo? Qual è insomma il tipo di una funzione lambda?

La questione è cruciale nei linguaggi fortemente tipizzati, dove è necessario identificare sempre il tipo di un oggetto per poterlo passare.

In Scala, che è un linguaggio fortemente tipizzato, la cosa viene risolta in modo elegante, cioè dando al tipo "funzione" una sintassi propria. Di seguito l'esempio di una High Order Function, cioè di una funzione che prende in input un'altra funzione:

function def highOrderFunction(func: Double => Double, v: Double ):String = {
        
        val result = func(v)
        result.toString();
        
}

Il primo parametro dice che deve essere passata una "funzione" che ritorna un numero decimale Double e prende in input un altro Double. Quindi, in Scala, la domanda non ha molto senso: il tipo di una funzione non è necessario specificarlo, perché il linguaggio ha una sintassi propria per definire un placeholder di funzione, che è il seguente:

(Input Parameters) => (Expression)


Però in altri linguaggi la questione si pone eccome. Ad esempio in C#. In questo linguaggio una funzione lambda viene definita come segue:

(double radius) => { radius * radius * Math.PI}


Ma se voglio "tradurre" la funzione Scala che abbiamo visto sopra (highOrderFunction) in C#, ho bisogno di sapere di che "tipo" è la funzione lambda.

Bene, in C# esistono due tipi (in realtà sono delegates) che rappresentano procedure e funzioni, sia che queste siano normali, e quindi nominali, sia che siano anonime, o lambda. Consideriamo di avere una procedura "VoidProcedure", che prende in input un intero, e una funzione "SimpleFunction" che prende in input un intero e ritorna un decimale:

static void VoidProcedure(int i)
{
    Console.WriteLine(i);
}

static double SimpleFunction(int i)
{
    return (double)i/2;
}


I tipi che le possono rappresentare sono:
Per cui si può scrivere un programma per invocarle indirettamente attraverso questi due nuovi tipi (delegati):

static void Main(string[] args)
{
    Action myAction = new Action(VoidProcedure);
    myAction.Invoke(123);           // Prints out "123"

    Func myFunc = new Func(SimpleFunction);
    Console.WriteLine(myFunc(5));   // Prints out "2.5"

    Console.ReadKey();
}


Ora, utilizzando al posto delle funzioni, le funzioni lambda, lo stesso programma diventa:

static void Main(string[] args)
{
    Action myAction = new Action(

        // Lambda procedure
        (i) => Console.WriteLine(i)

        );
    myAction.Invoke(123);           // Prints out "123"

    Func myFunc = new Func(
               
        // Lambda function
        (i) => (double)i/2
                
        );
    Console.WriteLine(myFunc(5));   // Prints out "2.5"

    Console.ReadKey();
}


In Java, i tipi lambda sono addirittura sei:

  • Predicate: una funzione che torna un booleano (normalmente per fare query)
  • Consumer: simile alla Action di cui sopra
  • Function: simile alla Func di cui sopra
  • Supplier: una funzione factory, che genera oggetti di tipo
  • UnaryOperator: funzione unaria che trasforma un oggetto
  • BinaryOperator: funzione binaria che trasforma due parametri in un valore di ritorno.

Cosicché il nostro esempio, in Java8, può scriversi come:

package lambdajava;

import java.util.function.Function;

public class LambdaJava {

    public static void main(String[] args) {

        byte[] ch = null;
        Function<Integer, Double> myFunc = i -> (double)i/2;
        System.out.println(myFunc.apply(5));
        
    }
      
}