Funzione di creazione di funzioni, equivalente in lingue compilate

2

Sono nuovo nelle lingue compilate. Sto imparando C. Sono abituato a scrivere codice in python.

Mi chiedevo se esistesse un metodo equivalente o di sostituzione nelle lingue compilate per le funzioni in grado di creare una funzione e di restituirla.

In python si può scrivere:

def genAdder (p):
    def adder (n):
        return n + p
    return adder

addFive = genAdder(5)
print(addFive(7))     # prints 12

È possibile fare una cosa del genere in C, o C ++ o in qualsiasi altro linguaggio compilato? Se sì, coinvolge la generazione del codice durante l'esecuzione? Se no, ci sono sostituzioni per situazioni in cui ciò è utile? (Può essere usato a scopo di performance, per evitare di rifare i calcoli)

    
posta loxaxs 18.10.2016 - 21:17
fonte

6 risposte

6

Sì, molte lingue compilate supportano funzioni di ordine superiore. No, raramente se mai eseguono la generazione del codice runtime.

In C, "puntatore funzione" è il termine di ricerca appropriato. C ++ supporta anche i puntatori di funzione, sebbene ci sia un certo numero di approcci (e librerie) alternativi per supportare un comportamento simile. Anche se generalmente non sono così puliti come il supporto di altre lingue per questo genere di cose a causa della loro storia. Java e C # in particolare gestiscono meglio questo.

E naturalmente i linguaggi funzionali effettivi hanno un grande supporto per questo, e sono spesso compilati.

Come usare p nella definizione interna, cioè una chiusura. Sono ben studiati e ben noti. Quando ho usato C ++ boost::bind fornito un meccanismo per fare qualcosa di simile. In queste lingue, generalmente è necessario creare un nuovo oggetto / classe per contenere la variabile "memorizzata" e la funzione secondaria.

    
risposta data 18.10.2016 - 21:25
fonte
5

Molte lingue compilate supportano questo. C non.

La funzione che stai cercando si chiama "chiusura lessicale" o "chiusura" (o talvolta "lambda", perché i designer di linguaggio amano usare parole diverse per lo stesso concetto in lingue diverse).

Non implica la generazione del codice in fase di runtime; coinvolge il codice trasformazione in fase di compilazione. Un compilatore supporta le chiusure (funzioni nidificate) riscrivendo il codice in una classe oggetto il cui stato è l'insieme di variabili che "chiude" dall'ambito esterno (da cui il nome, chiusura,) e il cui codice è la funzione nidificata, e quindi istanziazione trasparente di un oggetto di quella classe che può essere usato come una funzione.

I dettagli esatti di come viene eseguita variano da una lingua all'altra, ma lo schema di base è abbastanza coerente. Anche Python lo fa in un modo molto simile , tranne che non si preoccupa di creare una classe per l'oggetto funzione / stato generato .

Il linguaggio C in particolare non ha supporto per chiusure, ed è improbabile che ne abbia mai uno, perché il tipo di riscrittura del codice richiesto per farle funzionare viola la filosofia di progettazione del linguaggio, che tutto ciò che fa il codice quando viene eseguito dovrebbe essere facilmente leggibile leggendo la fonte.

    
risposta data 18.10.2016 - 22:07
fonte
4

Il codice qui sotto è C # valido - senza la cerimonia - testato su LinqPad:

Func<int, Func<int, int>> getAdder = (p) =>
    new Func<int, int> ((n) => n + p);

Func<int, int> addFive = getAdder(5);
Console.WriteLine(addFive(7)); // prints 12

Mentre è vero che il C # è (solitamente) compilato JIT, questo frammento non richiede la generazione del codice in fase di runtime, solo al momento della compilazione. Il codice creerà invece classi anonime nascoste per contenere i metodi.

Il compilatore creerà la seguente struttura (supponendo che il metodo con il codice precedente sia Main ):

[System.Runtime.CompilerServices.CompilerGenerated]
private sealed class adderClass
{
    public int p;
    internal int adderMethod(int n)
    {
        return n + this.p;
    }
}

[System.Runtime.CompilerServices.CompilerGenerated]
[Serializable]
private sealed class getAdderClass
{
    public static readonly getAdderClass Instance;
    static getAdderClass()
    {
        Instance = new getAdderClass();
    }
    internal Func<int, int> getAdderMethod(int p)
    {
        return new Func<int, int>(new adderClass{p = p}.adderMethod);
    }
}

public void Main()
{
    var getAdderFunc = new Func<int, Func<int, int>>(getAdderClass.Instance.getAdderMethod);
    var adderFunc = getAdderFunc(5);
    Console.WriteLine(adderFunc (7)); // prints 12
}

Il codice precedente è stato anche testato su LinqPad. Ho aggiunto i nomi delle classi e dei membri a scopo illustrativo. Il compilatore userà nomi come <>c__DisplayClass0_0 che non possono essere prodotti dal codice normale.

Come per Func , l'implementazione usa un riferimento all'oggetto di destinazione e un puntatore al metodo, quel puntatore non può essere ottenuto con il normale C #, invece Func e altri tipi di delegate forniscono un'astrazione sicura .

Voglio ribadire che la trasformazione presentata sopra avviene in fase di compilazione, non in fase di runtime.

Informazioni sull'utilità dei tipi delegate , sono come funzionano gli eventi in .NET .

    
risposta data 18.10.2016 - 21:59
fonte
3

Si noti che il codice stesso non cambia mai. Ad esempio, non cambierà improvvisamente in return n * p . Devi solo ricordare il valore di p quando è stato chiamato genAdder . Ciò che viene effettivamente restituito qui è una struttura di dati contenente il valore corrente di p e un puntatore a funzione di un certo codice fisso contenente la definizione fissa di adder . Se vuoi effettivamente generare un nuovo codice, devi usare compilare o eval .

Nota che la tua versione python in realtà non genera alcun nuovo codice ogni volta che viene chiamata la funzione. Sarebbe estremamente inefficiente. Genererà il codice di chiusura solo una volta, proprio come le lingue compilate. L'unica differenza è che aspetta fino alla prima volta che è necessaria in fase di runtime.

    
risposta data 18.10.2016 - 22:13
fonte
2

Nelle lingue che non supportano le chiusure, poiché la funzione non può "afferrare" i valori fuori dal contesto di chiusura, è necessario sapere quali sono i requisiti dei dati e crearli al volo. Il tuo esempio lo espone notevolmente. Chiamare genAdder ha per fare un'allocazione dell'heap qualche volta per memorizzare il valore 5. In C ++ il tuo adder sarebbe una classe dedicata con un campo int. In C avresti qualcosa di simile:

public int *alloc_adder_data (int value){
     //shove a malloc call here, return pointer
}
public int call_adder (int *adder_data, int num){
     return num + *adder_data
}

Quindi, se dovessi usare il sommatore come valore di prima classe da qualche parte, come per esempio mapparlo su un elenco o qualcosa del genere, puoi usare il puntatore della funzione su call_adder e passarlo alla funzione mappa (questo parla di quando vuoi tornare ai giorni del pitone dolce).

    
risposta data 18.10.2016 - 21:56
fonte
1

C ++ 11 ora supporta lambdas e il codice che hai scritto nella tua domanda può essere scritto in C ++ 11 in questo modo:

std::function<int(int)> genAdder(int p)
{
    return [p](int n) { return n + p ; };
}

void main()
{
    auto addFive = genAdder(5)
    count << addFive(7);     # prints 12
}
    
risposta data 19.10.2016 - 03:28
fonte

Leggi altre domande sui tag