Quali strumenti ci sono per la programmazione funzionale in C?


153

Ultimamente ho pensato molto a come fare la programmazione funzionale in C ( non C ++). Ovviamente, C è un linguaggio procedurale e non supporta realmente la programmazione funzionale in modo nativo.

Esistono estensioni di compilatore / linguaggio che aggiungono alcuni costrutti di programmazione funzionale al linguaggio? GCC fornisce funzioni nidificate come estensione del linguaggio; Le funzioni nidificate possono accedere alle variabili dal frame dello stack padre, ma questo è ancora molto lontano dalle chiusure mature.

Ad esempio, una cosa che penso possa essere davvero utile in C è che ovunque ci si aspetta un puntatore a funzione, si potrebbe essere in grado di passare un'espressione lambda, creando una chiusura che decade in un puntatore a funzione. C ++ 0x includerà espressioni lambda (che penso siano fantastiche); tuttavia, sto cercando strumenti applicabili alla scala C.

[Modifica] Per chiarire, non sto cercando di risolvere un particolare problema in C che sarebbe più adatto alla programmazione funzionale; Sono solo curioso di sapere quali strumenti ci sono là fuori se volessi farlo.


1
Invece di cercare hack ed estensioni non portatili per provare a trasformare C in qualcosa che non lo è, perché non usi semplicemente un linguaggio che fornisce la funzionalità che stai cercando?
Robert Gamble,

Vedi anche la domanda correlata: < stackoverflow.com/questions/24995/… >
Andy Brice,

@AndyBrice Quella domanda riguarda una lingua diversa e in realtà non sembra avere senso (C ++ non ha un "ecosistema" nello stesso senso di.
Kyle Strand

Risposte:


40

FFCALL ti consente di costruire chiusure in C - callback = alloc_callback(&function, data)restituisce un puntatore a funzione che callback(arg1, ...)equivale a chiamare function(data, arg1, ...). Dovrai comunque gestire manualmente la garbage collection.

Allo stesso modo, sono stati aggiunti blocchi al fork di GCC di Apple; non sono puntatori di funzioni, ma ti permettono di aggirare lambdas evitando la necessità di costruire e liberare manualmente le variabili catturate a mano (effettivamente, si verificano alcune copie e il conteggio dei riferimenti, nascosto dietro alcune librerie di zucchero sintattico e runtime).


89

Puoi usare le funzioni nidificate di GCC per simulare espressioni lambda, infatti, ho una macro per farlo per me:

#define lambda(return_type, function_body) \
  ({ \
    return_type anon_func_name_ function_body \
    anon_func_name_; \
  })

Usa così:

int (*max)(int, int) = lambda (int, (int x, int y) { return x > y ? x : y; });

12
Questo è davvero fantastico. Sembra __fn__solo un nome arbitrario per la funzione definita all'interno del blocco ({... }), non qualche estensione GCC o macro predefinita? La scelta del nome per __fn__(assomigliando molto alla definizione di GCC) mi ha davvero fatto grattare la testa e cercare nella documentazione di GCC per nessun effetto positivo.
FooF

1
Purtroppo, questo non funziona in pratica: se vuoi che la chiusura catturi qualcosa, richiede uno stack eseguibile, che è una terribile pratica di sicurezza. Personalmente, non penso che sia affatto utile.
Demi

Non è utile da remoto, ma è divertente. Ecco perché l'altra risposta è accettata.
Joe D

65

La programmazione funzionale non riguarda le lambda, si tratta solo di funzioni pure. Pertanto, i seguenti promuovono ampiamente lo stile funzionale:

  1. Usa solo argomenti di funzioni, non usare lo stato globale.

  2. Ridurre al minimo gli effetti collaterali, ad esempio printf o qualsiasi IO. Restituisce i dati che descrivono IO che possono essere eseguiti invece di causare gli effetti collaterali direttamente in tutte le funzioni.

Questo può essere ottenuto in pianura, senza bisogno di magia.


5
Penso che tu abbia portato quella visione estrema della programmazione funzionale al punto di assurdità. A che serve una specifica pura, ad esempio, mapse non si hanno le strutture per passargli una funzione?
Jonathan Leonard,

27
Penso che questo approccio sia molto più pragmatico rispetto all'uso delle macro per creare un linguaggio funzionale all'interno di c. L'uso delle funzioni pure in modo appropriato ha maggiori probabilità di migliorare un sistema rispetto all'uso della mappa anziché per i loop.
Andy Fino

6
Sicuramente sai che la mappa è solo il punto di partenza. Senza funzioni di prima classe, qualsiasi programma (anche se tutte le sue funzioni sono "pure") sarà di ordine inferiore (nel senso della teoria dell'informazione) rispetto al suo equivalente con funzioni di prima classe. A mio avviso, è questo stile dichiarativo possibile solo con funzioni di prima classe che è il principale vantaggio di FP. Penso che stai facendo del male a qualcuno per insinuare che la verbosità che deriva dalla mancanza di funzioni di prima classe è tutto ciò che FP ha da offrire. Va notato che storicamente il termine FP implicava funzioni di prima classe più che purezza.
Jonathan Leonard,

PS Il commento precedente era dal cellulare: la grammatica irregolare non era intenzionale.
Jonathan Leonard,

1
La risposta di Andy Tills mostra una visione molto pragmatica delle cose. Andare "Puro" in C non richiede nulla di speciale e offre un grande vantaggio. Funzioni di prima classe, a paragone di "confort della vita". È conveniente, ma è pratico adottare uno stile di programmazione puro. Mi chiedo perché nessuno discuti delle chiamate di coda in relazione a tutto ciò. Coloro che desiderano funzioni di prima classe dovrebbero anche richiedere chiamate di coda.
BitTickler

17

Il libro di Hartel & Muller, Functional C , al giorno d'oggi (2012-01-02) è disponibile all'indirizzo: http://eprints.eemcs.utwente.nl/1077/ (esiste un collegamento alla versione PDF).


2
Non c'è alcun tentativo di qualcosa di funzionale in questo libro (in relazione alla domanda).
Eugene Tolmachev,

4
Sembra che tu abbia ragione. In effetti, la prefazione afferma che lo scopo del libro è insegnare la programmazione imperativa dopo che lo studente ha già familiarizzato con la programmazione funzionale. Cordiali saluti, questa è stata la mia prima risposta in SO ed è stata scritta come una risposta solo perché non avevo richiesto la reputazione per commentare una risposta precedente con link marcio. Mi chiedo sempre perché le persone mantengano +1 questa risposta ... Per favore, non farlo! :-)
FooF

1
Forse il libro avrebbe dovuto essere meglio chiamato Programmazione post-funzionale (prima di tutto perché "Imperativo C" non sembra sexy, in secondo luogo perché assume familiarità con il paradigma di programmazione funzionale, in terzo luogo come un gioco di parole perché il paradigma imperativo sembra un declino degli standard e funzionalità corretta, e forse quarto per fare riferimento alla nozione di programmazione postmoderna di Larry Wall - sebbene questo libro sia stato scritto prima dell'articolo / presentazione di Larry Wall).
FooF

E questa è una risposta duplicata
PhilT l'

8

Prerequisito per lo stile di programmazione funzionale è una funzione di prima classe. Potrebbe essere simulato in C portatile se tolleri il prossimo:

  • gestione manuale di attacchi di ambito lessicale, ovvero chiusure.
  • gestione manuale della durata delle variabili funzione.
  • sintassi alternativa della funzione application / call.
/* 
 * with constraints desribed above we could have
 * good approximation of FP style in plain C
 */

int increment_int(int x) {
  return x + 1;
}

WRAP_PLAIN_FUNCTION_TO_FIRST_CLASS(increment, increment_int);

map(increment, list(number(0), number(1)); // --> list(1, 2)


/* composition of first class function is also possible */

function_t* computation = compose(
  increment,
  increment,
  increment
);

*(int*) call(computation, number(1)) == 4;

il tempo di esecuzione per tale codice potrebbe essere piccolo come quello di seguito

struct list_t {
  void* head;
  struct list_t* tail;
};

struct function_t {
   void* (*thunk)(list_t*);
   struct list_t* arguments;
}

void* apply(struct function_t* fn, struct list_t* arguments) {
  return fn->thunk(concat(fn->arguments, arguments));
}

/* expansion of WRAP_PLAIN_FUNCTION_TO_FIRST_CLASS */
void* increment_thunk(struct list_t* arguments) {
  int x_arg = *(int*) arguments->head;
  int value = increment_int(x_arg);
  int* number = malloc(sizeof *number);

  return number ? (*number = value, number) : NULL;
}

struct function_t* increment = &(struct function_t) {
  increment_thunk,
  NULL
};

/* call(increment, number(1)) expands to */
apply(increment, &(struct list_t) { number(1), NULL });

In sostanza imitiamo la funzione di prima classe con le chiusure rappresentate come coppia di funzioni / argomenti più un mucchio di macro. Il codice completo può essere trovato qui .


7

La cosa principale che viene in mente è l'uso di generatori di codice. Saresti disposto a programmare in un linguaggio diverso che ha fornito la programmazione funzionale e quindi generare il codice C da quello?

Se questa non è un'opzione interessante, allora potresti abusare di CPP per ottenere parte del percorso lì. Il sistema macro dovrebbe consentire di emulare alcune idee di programmazione funzionale. Ho sentito dire che gcc è implementato in questo modo ma non ho mai verificato.

C può ovviamente passare le funzioni usando i puntatori a funzione, i problemi principali sono la mancanza di chiusure e il sistema di tipi tende a mettersi in mezzo. Potresti esplorare sistemi macro più potenti di CPP come M4. Credo che alla fine, ciò che sto suggerendo è che la vera C non è all'altezza del compito senza grandi sforzi ma potresti estendere C per renderlo all'altezza del compito. Quell'estensione assomiglierebbe di più a C se usi CPP o potresti andare dall'altra parte dello spettro e generare codice C da qualche altra lingua.


Questa è la risposta più onesta. Cercare di scrivere C in modo funzionale significa combattere contro il design e la struttura del linguaggio stesso.
josiah,

5

Se si desidera implementare chiusure, è necessario diventare entusiasti con il linguaggio assembly e lo scambio / gestione dello stack. Non sconsigliarlo, solo dire che è quello che devi fare.

Non sono sicuro di come gestirai le funzioni anonime in C. Su una macchina von Neumann, però, potresti fare funzioni anonime in asm.



2

Il linguaggio Felix viene compilato in C ++. Forse potrebbe essere una pietra miliare, se non ti dispiace C ++.


9
Because¹ perché α) L'autore ha menzionato esplicitamente «non C ++», β) Il C ++ prodotto da un compilatore non sarebbe un «C ++ leggibile dall'uomo». γ) Lo standard C ++ supporta una programmazione funzionale, non è necessario utilizzare un compilatore da una lingua all'altra.
Ciao Angelo

Una volta che hai escogitato un linguaggio funzionale per mezzo di macro o simili, implica automaticamente che non ti interessa così tanto per C. Questa risposta è valida e va bene perché anche C ++ è iniziato come macro party su C. Se tu percorrere quella strada abbastanza lontano, ti ritroverai con una nuova lingua. Quindi si tratta solo di una discussione sul back-end.
BitTickler

1

Bene, alcuni linguaggi di programmazione sono scritti in C. E alcuni di essi supportano funzioni come cittadini di prima classe, le lingue in quella zona sono ecl (embriabble common lisp IIRC), Gnu Smalltalk (gst) (Smalltalk ha blocchi), quindi ci sono librerie per "chiusure" ad es. in glib2 http://library.gnome.org/devel/gobject/unstable/chapter-signal.html#closure che almeno si avvicina alla programmazione funzionale. Quindi forse usare alcune di queste implementazioni per eseguire la programmazione funzionale potrebbe essere un'opzione.

Bene o puoi andare ad imparare Ocaml, Haskell, Mozart / Oz o simili ;-)

Saluti


Ti dispiacerebbe dirmi cosa c'è di così terribile nell'uso delle librerie che almeno supportano lo stile di programmazione FP?
Friedrich,

1

Il modo in cui ho fatto la programmazione funzionale in C è stato quello di scrivere un interprete di linguaggio funzionale in C. L'ho chiamato Fexl, che è l'abbreviazione di "Function EXpression Language".

L'interprete è molto piccolo, compilando fino a 68K sul mio sistema con -O3 abilitato. Non è nemmeno un giocattolo - lo sto usando per tutto il nuovo codice di produzione che scrivo per la mia attività (contabilità basata sul web per partnership di investimento).

Ora scrivo il codice C solo per (1) aggiungere una funzione integrata che chiama una routine di sistema (ad esempio fork, exec, setrlimit, ecc.), Oppure (2) ottimizzare una funzione che potrebbe altrimenti essere scritta in Fexl (ad es. Ricerca per una sottostringa).

Il meccanismo del modulo si basa sul concetto di "contesto". Un contesto è una funzione (scritta in Fexl) che associa un simbolo alla sua definizione. Quando leggi un file Fexl, puoi risolverlo con qualsiasi contesto ti piaccia. Ciò consente di creare ambienti personalizzati o eseguire codice in un "sandbox" limitato.

http://fexl.com


-3

Cos'è che vuoi rendere funzionale, la sintassi o la semantica di C? La semantica della programmazione funzionale potrebbe certamente essere aggiunta al compilatore C, ma quando avessi finito, avresti essenzialmente l'equivalente di uno dei linguaggi funzionali esistenti, come Scheme, Haskell, ecc.

Sarebbe meglio usare il tempo per imparare la sintassi di quelle lingue che supportano direttamente quelle semantiche.


-3

Non so di C. Ci sono alcune caratteristiche funzionali in Objective-C, GCC su OSX supporta anche alcune funzionalità, tuttavia consiglierei di nuovo di iniziare a usare un linguaggio funzionale, ce ne sono molte menzionate sopra. Ho iniziato personalmente con Schema, ci sono alcuni libri eccellenti come The Little Schemer che possono aiutarti a farlo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.