Funtore (programmazione)

costrutto di programmazione

Una funzione oggetto, anche chiamata funtore[1] è un costrutto di programmazione che permette ad un oggetto di essere invocato o chiamato come se fosse una funzione ordinaria, solitamente con la stessa sintassi.

Descrizione

modifica

Un uso tipico del funtore è nella scrittura delle funzioni di callback. Nei linguaggi di programmazione procedurale, come il C, può essere accompagnata dall'uso di puntatori a funzione. In ogni caso può essere difficile o scomodo passare o ritornare lo stato di una funzione. Un funtore risolve questi problemi se la funzione rispetta il Façade pattern per un oggetto.

I più moderni linguaggi orientati agli oggetti come C++, Java, Python, Ruby e Lisp supportano la definizione di funtori e ne rendono significativo l'uso.

Origini

modifica

Smalltalk fu uno dei primi linguaggi a supportare i funtori attraverso l'uso di blocchi che sono parte integrante della sintassi del linguaggio. Per esempio uno può fornire un funtore come argomento a collezioni di oggetti per fornire filtraggio e ordinamento. È una realizzazione perfetta dello strategy pattern.

Funtori in C e C++

modifica

Consideriamo l'esempio dell'ordinamento che usa una funzione di callback per definire una relazione d'ordine tra coppie di valori. Un programma C che usa i puntatori a funzione apparirebbe così:

/* Callback function */
int compare_function(int A, int B) {
  return (A < B);
}
...
/* Declaration of C sorting function */
void sort_ints(int* begin_items, int num_items, int (*cmpfunc)(int, int) );
...
int main() {
    int items[] = {4, 3, 1, 2};
    sort_ints(items, sizeof(items)/sizeof(int), compare_function);
}

In C++ un funtore può essere usato al posto di una funzione ordinaria attraverso la definizione di una classe che fa overloading dell'operatore "chiamata a funzione" definendo una funzione membro operator(). In C++ questo è chiamato class type functor, e può apparire così:

class compare_class {
  public:
  bool operator()(int A, int B) {
    return (A < B);
  }
};
...
// Declaration of C++ sorting function.
template <class ComparisonFunctor> 
void sort_ints(int* begin_items, int num_items, ComparisonFunctor c);
...
int main() {
    int items[] = {4, 3, 1, 2};
    compare_class functor;
    sort_ints(items, sizeof(items)/sizeof(int), functor);
}

Nota che la sintassi per fornire la callback alla funzione sort_ints() è identica, ma viene passato un oggetto invece di un puntatore a funzione. Quando viene invocata la funzione di callback viene eseguita come ogni altra funzione membro, e ha pieno accesso agli altri membri (dati o funzioni) dell'oggetto.

È possibile usare funtori in altre situazioni oltre che come funzioni di callback (sebbene l'abbreviazione funtore non venga normalmente usata). Continuiamo l'esempio.

  functor_class Y;
  int result = Y( a, b );

In aggiunta ai funtori per le classi, sono possibili anche altri tipi di funzioni oggetto in C++. Questi possono trarre vantaggio dalle facilitazioni tipo il puntatore a membro o la programmazione a template. L'espressività dei template permette anche alcune tecniche di programmazione funzionale, come definire funtori in termini di altri funtori (come nella composizione di funzioni). La maggior parte della standard template library (STL) del C++ fa uso intensivo dei funtori.

Performance

modifica

Un vantaggio dei funtori in C++ è la performance perché diversamente da un puntatore a funzione una funzione oggetto può essere scritta inline. Per esempio consideriamo una semplice funzione che incrementa il suo argomento implementata come una funzione oggetto:

struct IncrementFunctor {
  void operator()(int &i) { ++i; }
};

e come funzione libera

void increment_function(int &i) { ++i; }

Richiamiamo la funzione std::for_each():

template<typename InputIterator, typename Function>
Function for_each(InputIterator first, InputIterator last, Function f) {
  for ( ; first != last; ++first)
    f(*first);
  return f;
}

Supponiamo di applicare std::for_each() in questo modo:

int A[] = {1, 4, 2, 8, 5, 7};
const int N = sizeof(A) / sizeof(A[0]);
for_each(A, A + N, IncrementFunctor());
for_each(A, A + N, increment_function);

Entrambe le chiamate a for_each funzionerà come atteso. La prima chiamata sarà a questa versione:

IncrementFunctor for_each<int*,IncrementFunctor>(int*, int*, IncrementFunctor)

mentre la seconda a questa versione:

void(*)(int&) for_each<int*,void(*)(int&)>(int*, int*, void(*)(int&))

All'interno di for_each<int*,IncrementFunctor>(), il compilatore sarà in grado di mettere inline la funzione oggetto perché la funzione è nota a compile time mentre in for_each<int*,void(*)(int&)>() la funzione non può essere nota a compile time e quindi non può essere messa inline.

Attualmente una funzione può facilmente essere nota a compile time e il compilatore la metterà inline, se viene istruito. L'unico requisito è che il compilatore abbia osservato la definizione della funzione, e questo si applica ugualmente dentro una classe o fuori. Nel caso in cui non si metta inline, il linker viene istruito per ignorare "silenziosamente" definizioni multiple della stessa funzione da diverse unità di compilazione, senza produrre un errore, ma solo se la funzione in questione è una funzione di una classe. Il linker non scarterà definizioni multiple della stessa funzione se non è una funzione di classe.

Mantenimento dello stato

modifica

Un altro vantaggio dei funtori è che possono mantenere lo stato che riguarda l'operatore () attraverso le chiamate. L'inconveniente è che le copie di un funtore devono condividere lo stato per funzionare correttamente. Agli algoritmi STL è consentito fare delle copie. Per esempio il seguente codice definisce un generatore che conta da 10 in su ed è chiamato 11 volte.

#include <iostream>
#include <iterator>
#include <algorithm>
 
class countfrom {
private:
  int &count;
public:
  countfrom(int &n) : count(n) {}
  int operator()() { return count++; }
};
 
int main() {
  int state(10);
  std::generate_n(std::ostream_iterator<int>(std::cout, "\n"), 11, countfrom(state));
  return 0;
}

Funtori in D

modifica

Il linguaggio D fornisce molti modi di dichiarare i funtori. Lo stile Lisp/Python e quello C#.

bool find(T)(T[] haystack, bool delegate(T) needle_test) {
  foreach ( straw; haystack ) {
    if ( needle_test(straw) )
      return true;
  }
  return false;
}

void main() {
    int[] haystack = [345,15,457,9,56,123,456];
    int   needle = 123;
    bool needleTest(int n) {
      return n == needle;
    }
    assert(
      find(haystack, &needleTest)
    );
}

La differenza viene automaticamente e conservativamente determinata dal compilatore. Il D supporta anche le funzioni letterali, che permette una definizione in stile lambda:

void main() {
    int[] haystack = [345,15,457,9,56,123,456];
    int   needle = 123;
    assert(
       find(haystack, (int n) { return n == needle; })
    );
}

Per permettere al compilare di mettere il codice in-line (vedi sopra), i funtori possono anche essere specificati nello stile C++ usando l'overload degli operatori.

bool find(T,F)(T[] haystack, F needle_test) {
  foreach ( straw; haystack ) {
    if ( needle_test(straw) )
      return true;
  }
  return false;
}

void main() {
    int[] haystack = [345,15,457,9,56,123,456];
    int   needle = 123;
    class NeedleTest {
      int needle;
      this(int n) { needle = n; }
      bool opCall(int n) {
        return n == needle;
      }
    }
    assert(
      find(haystack, new NeedleTest(needle))
    );
}

Funtori in Java

modifica

Siccome Java non ha funzioni di prima classe, i funtori sono solitamente espressi da un'interfaccia con un singolo metodo, tipicamente con l'implementazione rappresentata da una classe interna anonima.

Un esempio dalla Java standard library è nella funzione java.util.Collections.sort() che prende una List e un funtore il cui ruolo è di confrontare gli oggetti nella lista. Ma siccome Java non ha funzioni di prima classe, la funzione è parte dell'interfaccia di confronto. Questo può essere usato come segue.

List<String> list = Arrays.asList("10", "1", "20", "11", "21", "12");
		
Collections.sort(list, new Comparator<String>() {
    public int compare(String o1, String o2) {
        return Integer.valueOf(o1).compareTo(Integer.valueOf(o2));
    }
});

Un funtore può essere anche costruito implementando l'interfaccia java.lang.Runnable ed attivato tramite java.lang.Thread:

import java.lang.*;

class Funtore implements Runnable{
    private boolean eseguita;
    private Object ris;       // può essere "Object" come qualsiasi altra classe
    /*  */                    // altri campi dati

    public Funtore(/* */) {   // signature del costruttore
         // inizializza i campi dati dell'oggetto Funtore
    }

    public synchronized void run(){
        if (eseguita)
            return;
        eseguita = true;
        // svolge il lavoro della funzione
        // scrive il risultato del lavoro svolto nella variabile "ris"
    }

    public synchronized Object getRis() {
        if (!eseguita)
            throw new RuntimeException();
        else
            return ris;
    }

    public synchronized boolean Eseguita(){
        return eseguita;
    }
}

public class Test{
    public static void main(String[] args){
        Object ris;
        Funtore f = new Funtore(/* */);
        Thread t = new Thread(f);
        t.start();
        try { t.join(); }
        catch (InterruptedException e) { e.printStackTrace(); }
        ris = f.getRis();
    }
}

Funtori in Python

modifica

In Python, le funzioni sono oggetti, come le stringhe, i numeri le liste e così via. Questa caratteristica elimina la necessità di creare una funzione oggetto in molti casi. Comunque ogni oggetto con un metodo __call__() può essere chiamato usando una sintassi di chiamata a funzione.

Un esempio è questa classe Accumulator (basata sullo studio di Paul Graham sulla sintassi dei linguaggi di programmazione e sulla chiarezza([2]:

class Accumulator(object):
    def __init__(self, n):
        self.n = n
    def __call__(self, x):
        self.n += x
        return self.n

Un esempio di questo nell'uso pratico (usando l'interprete interattivo):

>>> a = Accumulator(4)
>>> a(5)
9
>>> a(2)
11
>>> b = Accumulator(42)
>>> b(7)
49

Un altro modo di costruire un funtore in Python è di usare un'inclusione:

def Accumulator(n):
    def inc(x):
        inc.n += x
        return inc.n
    inc.n = n
    return inc

Funtori in Ruby

modifica

Ruby ha un numero di oggetti che possono essere considerati funtori, in particolare gli oggetti Method e Proc. Ruby ha anche due tipi di oggetti che possono essere ritenuti dei semi-funtori: UnboundMethod e block. UnboundMethods deve essere prima collegato ad un oggetto (diventando un Method) prima di poter essere usato come funtore. I Block possono essere chiamati come i funtori, ma per poter essere usati in ogni altra accezione come un oggetto (per es. passati come argomento) devono prima essere convertiti in un Proc. Più recentemente, i simboli (acceduti attraverso l'indicatore : possono anche essere convertiti in Proc. Usando l'operatore unario di ruby &, equivalente a chiamare to_proc su un oggetto, e assumendo che il metodo esista, il progetto Ruby Extension Project ha creato un semplice hack.

class Symbol
   def to_proc
      proc { |obj, *args| obj.send(self, *args) }
   end
end

Ora, il metodo foo può essere un funtore, es. un Proc, via &:foo e usato con takes_a_functor(&:foo). Symbol.to_proc è stato ufficialmente aggiunto a Ruby l'11 giugno, 2006 durante RubyKaiga2006[3].

A causa delle varietà delle forme, il termine funtore non viene generalmente usato in Ruby per indicare una funzione oggetto. Piuttosto è diventato un tipo di dispatch delegation introdotta dal progetto Ruby Facets. La definizione più semplice di questo è:

class Functor
  def initialize(&func)
    @func = func
  end
  def method_missing(op, *args, &blk)
    @func.call(op, *args, &blk)
  end
end

Questo uso è più simile a quelli usati dai linguaggi di programmazioni funzionali, come ML e la terminologia matematica originale.

  1. ^ (EN) http://www.parashift.com/c++-faq-lite/pointers-to-members.html#faq-33.10 Archiviato il 4 giugno 2010 in Internet Archive.
  2. ^ (EN) here
  3. ^ (EN) Copia archiviata, su redhanded.hobix.com. URL consultato il 20 agosto 2006 (archiviato dall'url originale il 20 agosto 2006).]

Voci correlate

modifica

Collegamenti esterni

modifica
  Portale Informatica: accedi alle voci di Wikipedia che trattano di informatica