Alat apa yang ada untuk pemrograman fungsional dalam C?

153

Saya telah berpikir banyak akhir-akhir ini tentang bagaimana cara melakukan pemrograman fungsional dalam C ( bukan C ++). Jelas, C adalah bahasa prosedural dan tidak benar-benar mendukung pemrograman fungsional secara asli.

Apakah ada ekstensi kompiler / bahasa yang menambahkan beberapa konstruksi pemrograman fungsional ke bahasa? GCC menyediakan fungsi bersarang sebagai ekstensi bahasa; fungsi bersarang dapat mengakses variabel dari kerangka tumpukan induk, tetapi ini masih jauh dari penutupan yang matang.

Sebagai contoh, satu hal yang saya pikir bisa sangat berguna dalam C adalah bahwa di mana pun di mana pointer fungsi diharapkan, Anda bisa dapat melewati ekspresi lambda, membuat penutupan yang meluruh menjadi pointer fungsi. C ++ 0x akan memasukkan ekspresi lambda (yang menurut saya mengagumkan); namun, saya mencari alat yang dapat digunakan untuk straight C.

[Sunting] Untuk memperjelas, saya tidak mencoba memecahkan masalah tertentu dalam C yang akan lebih cocok untuk pemrograman fungsional; Saya hanya ingin tahu tentang alat apa yang ada di luar sana jika saya ingin melakukannya.

Adam Rosenfield
sumber
1
Daripada mencari peretasan dan ekstensi non-portabel untuk mencoba mengubah C menjadi sesuatu yang bukan, mengapa Anda tidak menggunakan bahasa yang menyediakan fungsionalitas yang Anda cari?
Robert Gamble
Lihat juga pertanyaan terkait: < stackoverflow.com/questions/24995/… >
Andy Brice
@AndyBrice Pertanyaan itu adalah tentang bahasa yang berbeda dan tampaknya tidak masuk akal (C ++ tidak memiliki "ekosistem" dalam arti yang sama.
Kyle Strand

Jawaban:

40

FFCALL memungkinkan Anda membangun penutupan di C - callback = alloc_callback(&function, data)mengembalikan penunjuk fungsi sedemikian rupa sehingga callback(arg1, ...)setara dengan panggilan function(data, arg1, ...). Anda harus menangani pengumpulan sampah secara manual.

Terkait, blok telah ditambahkan ke garpu GCC Apple; mereka bukan fungsi pointer, tetapi mereka membiarkan Anda berkeliling lambdas sambil menghindari kebutuhan untuk membangun dan penyimpanan gratis untuk variabel yang ditangkap dengan tangan (secara efektif, beberapa penyalinan dan penghitungan referensi terjadi, tersembunyi di balik beberapa perpustakaan sintaksis gula dan runtime).

singkat
sumber
89

Anda dapat menggunakan fungsi bersarang GCC untuk mensimulasikan ekspresi lambda, pada kenyataannya, saya memiliki makro untuk melakukannya untuk saya:

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

Gunakan seperti ini:

int (*max)(int, int) = lambda (int, (int x, int y) { return x > y ? x : y; });
Joe D
sumber
12
Ini sangat keren. Tampaknya __fn__hanya nama arbitrer untuk fungsi yang didefinisikan dalam blok ({... }), bukan ekstensi GCC atau makro yang telah ditentukan? Pilihan nama untuk __fn__(terlihat sangat mirip dengan definisi GCC) benar-benar membuat saya menggaruk kepala saya dan mencari dokumentasi GCC tanpa efek yang baik.
FooF
1
Sayangnya, ini tidak bekerja dalam praktiknya: jika Anda ingin penutupnya menangkap sesuatu, itu membutuhkan tumpukan yang dapat dieksekusi, yang merupakan praktik keamanan yang mengerikan. Secara pribadi, saya pikir itu tidak berguna sama sekali.
Demi
Ini tidak berguna dari jarak jauh, tetapi menyenangkan. Itu sebabnya jawaban lain diterima.
Joe D
65

Pemrograman fungsional bukan tentang lambdas, itu semua tentang fungsi murni. Jadi berikut ini secara luas mempromosikan gaya fungsional:

  1. Hanya gunakan argumen fungsi, jangan gunakan negara global.

  2. Minimalkan efek samping yaitu printf, atau IO apa pun. Mengembalikan data yang menggambarkan IO yang dapat dieksekusi daripada menyebabkan efek samping secara langsung di semua fungsi.

Ini dapat dicapai dalam c sederhana, tidak perlu sihir.

Andy Hingga
sumber
5
Saya pikir Anda telah mengambil pandangan ekstrim pemrograman fungsional ke titik absurditas. Apa gunanya spesifikasi murni, misalnya map, jika seseorang tidak memiliki fasilitas untuk meneruskan fungsinya?
Jonathan Leonard
27
Saya pikir pendekatan ini jauh lebih pragmatis daripada menggunakan makro untuk membuat bahasa fungsional di dalam c. Menggunakan fungsi murni secara tepat lebih mungkin untuk meningkatkan sistem daripada menggunakan peta, bukan untuk loop.
Andy Hingga
6
Tentunya Anda tahu bahwa peta hanyalah titik awal. Tanpa fungsi kelas satu, program apa pun (bahkan jika semua fungsinya 'murni') akan lebih rendah (dalam arti teori informasi) daripada yang setara dengan fungsi kelas satu. Dalam pandangan saya, gaya deklaratif ini hanya mungkin dengan fungsi kelas satu yang merupakan manfaat utama FP. Saya pikir Anda melakukan tindakan merugikan seseorang untuk menyiratkan bahwa verbositas yang dihasilkan dari kurangnya fungsi kelas satu adalah semua yang ditawarkan FP. Perlu dicatat bahwa secara historis istilah FP menyiratkan fungsi kelas lebih dari sekadar kemurnian.
Jonathan Leonard
PS Komentar sebelumnya adalah dari ponsel - tata bahasa tidak teratur tidak disengaja.
Jonathan Leonard
1
Jawaban Andy Tills menampilkan pandangan yang sangat pragmatis tentang berbagai hal. Menjadi "Murni" dalam C tidak memerlukan sesuatu yang mewah dan menghasilkan manfaat besar. Fungsi kelas satu, dalam perbandingannya menyumbang "kenyamanan hidup". Memang mudah, tetapi mengadopsi gaya pemrograman murni itu praktis. Saya bertanya-tanya mengapa tidak ada yang membahas permintaan ekor sehubungan dengan semua ini. Mereka yang ingin fungsi kelas satu juga harus meminta panggilan ekor.
BitTickler
17

Buku Hartel & Muller, Functional C , saat ini (2012-01-02) dapat ditemukan di: http://eprints.eemcs.utwente.nl/1077/ (ada tautan ke versi PDF).

FooF
sumber
2
Tidak ada upaya apa pun yang berfungsi dalam buku ini (seperti yang terkait dengan pertanyaan).
Eugene Tolmachev
4
Sepertinya Anda benar. Memang, kata pengantar menyatakan tujuan dari buku ini adalah untuk mengajarkan pemrograman imperatif setelah siswa telah membiasakan diri dengan pemrograman fungsional. FYI, ini adalah jawaban pertama saya di SO dan itu ditulis sebagai jawaban hanya karena saya tidak memerlukan reputasi untuk mengomentari jawaban sebelumnya dengan tautan busuk. Saya selalu bertanya-tanya mengapa orang memberi +1 pada jawaban ini ... Tolong jangan lakukan itu! :-)
FooF
1
Mungkin buku itu seharusnya lebih baik disebut Pemrograman Post-Fungsional (pertama-tama karena "Imperatif C" tidak terdengar seksi, kedua karena mengasumsikan keakraban dengan paradigma pemrograman fungsional, ketiga sebagai pelesetan karena paradigma imperatif seperti penurunan standar dan fungsi yang benar, dan mungkin keempat untuk merujuk pada gagasan Larry Wall tentang pemrograman postmodern - meskipun buku ini ditulis sebelum artikel / presentasi Larry Wall).
FooF
Dan ini adalah jawaban rangkap
PhilT
8

Prasyarat untuk gaya pemrograman fungsional adalah fungsi kelas satu. Itu bisa disimulasikan dalam portable C jika Anda mentolerir selanjutnya:

  • manajemen manual dari pengikatan lingkup leksikal, alias penutupan.
  • manajemen variabel fungsi seumur hidup secara manual.
  • sintaks alternatif aplikasi fungsi / panggilan.
/* 
 * 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;

runtime untuk kode tersebut bisa sekecil satu di bawah ini

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 });

Intinya kita meniru fungsi kelas satu dengan penutup yang direpresentasikan sebagai pasangan fungsi / argumen plus sekelompok macroses. Kode lengkap dapat ditemukan di sini .

Viktor Shepel
sumber
7

Hal utama yang terlintas dalam pikiran adalah penggunaan generator kode. Apakah Anda bersedia memprogram dalam bahasa yang berbeda yang menyediakan pemrograman fungsional dan kemudian menghasilkan kode C dari itu?

Jika itu bukan pilihan yang menarik, maka Anda dapat menyalahgunakan CPP untuk ikut serta dalam perjalanan ke sana. Sistem makro seharusnya membiarkan Anda meniru beberapa ide pemrograman fungsional. Saya pernah mendengar bahwa gcc diimplementasikan dengan cara ini tetapi saya tidak pernah memeriksa.

C tentu saja dapat melewati fungsi menggunakan pointer fungsi, masalah utama adalah kurangnya penutupan dan sistem tipe cenderung menghalangi. Anda dapat menjelajahi sistem makro yang lebih kuat daripada CPP seperti M4. Saya kira pada akhirnya, apa yang saya sarankan adalah bahwa C benar tidak sesuai dengan tugas tanpa usaha keras tetapi Anda bisa memperluas C untuk membuatnya sesuai dengan tugas. Ekstensi itu akan terlihat paling seperti C jika Anda menggunakan CPP atau Anda bisa pergi ke ujung lain spektrum dan menghasilkan kode C dari beberapa bahasa lain.

Jason Dagit
sumber
Ini jawaban yang paling jujur. Mencoba untuk menulis C secara fungsional adalah untuk melawan desain dan struktur bahasa itu sendiri.
josiah
5

Jika Anda ingin menerapkan penutupan, Anda harus mengaduh dengan bahasa assembly dan menukar / menumpuk tumpukan. Tidak merekomendasikan menentangnya, hanya mengatakan itu yang harus Anda lakukan.

Tidak yakin bagaimana Anda akan menangani fungsi anonim di C. Pada mesin von Neumann, Anda bisa melakukan fungsi anonim dalam asm.

Paul Nathan
sumber
2

Bahasa Felix mengkompilasi ke C ++. Mungkin itu bisa menjadi batu langkah, jika Anda tidak keberatan C ++.

LennyStackOverflow
sumber
9
⁻¹ karena α) Penulis secara eksplisit menyebutkan «bukan C ++», β) C ++ yang diproduksi oleh kompiler tidak akan menjadi «C ++ yang dapat dibaca manusia». γ) Standar C ++ mendukung pemrograman fungsional, tidak perlu menggunakan kompiler dari satu bahasa ke bahasa lain.
Hi-Angel
Setelah Anda menggunakan bahasa fungsional dengan menggunakan makro atau sejenisnya, Anda secara otomatis menyiratkan bahwa Anda tidak terlalu peduli untuk C. Jawaban ini valid dan oke karena bahkan C ++ dimulai sebagai pesta makro di atas C. Jika Anda pergi jalan itu cukup jauh, Anda akan berakhir dengan bahasa baru. Maka itu hanya datang ke diskusi ujung belakang.
BitTickler
1

Yah beberapa bahasa pemrograman ditulis dalam C. Dan beberapa dari mereka mendukung fungsi sebagai warga negara kelas satu, bahasa di daerah itu adalah ecl (embedabble common lisp IIRC), Gnu Smalltalk (gst) (Smalltalk memiliki blok), kemudian ada perpustakaan untuk "closure" misalnya di glib2 http://library.gnome.org/devel/gobject/unstable/chapter-signal.html#closure yang setidaknya mendekati pemrograman fungsional. Jadi mungkin menggunakan beberapa implementasi tersebut untuk melakukan pemrograman fungsional dapat menjadi pilihan.

Baik atau Anda bisa belajar Ocaml, Haskell, Mozart / Oz atau sejenisnya ;-)

Salam

Friedrich
sumber
Maukah Anda memberi tahu saya apa yang salah dengan menggunakan perpustakaan yang setidaknya mendukung gaya pemrograman FP?
Friedrich
1

Cara saya melakukan pemrograman fungsional dalam bahasa C adalah menulis penerjemah bahasa fungsional dalam bahasa C. Saya menamakannya Fexl, yang merupakan kependekan dari "Function EXpression Language."

Interpreternya sangat kecil, mengkompilasi hingga 68K pada sistem saya dengan -O3 diaktifkan. Ini juga bukan mainan - saya menggunakannya untuk semua kode produksi baru yang saya tulis untuk bisnis saya (akuntansi berbasis web untuk kemitraan investasi.)

Sekarang saya menulis kode C hanya untuk (1) menambahkan fungsi built-in yang memanggil sistem rutin (misalnya fork, exec, setrlimit, dll.), Atau (2) mengoptimalkan fungsi yang dapat ditulis dalam Fexl (misalnya pencarian untuk substring).

Mekanisme modul didasarkan pada konsep "konteks". Konteks adalah fungsi (ditulis dalam Fexl) yang memetakan simbol ke definisinya. Saat Anda membaca file Fexl, Anda bisa menyelesaikannya dengan konteks apa pun yang Anda suka. Ini memungkinkan Anda membuat lingkungan khusus, atau menjalankan kode di "kotak pasir" terbatas.

http://fexl.com

Patrick Chkoreff
sumber
-3

Ada apa dengan C yang ingin Anda buat fungsional, sintaks atau semantik? Semantik pemrograman fungsional tentu dapat ditambahkan ke kompiler C, tetapi pada saat Anda selesai, Anda pada dasarnya akan memiliki setara dengan salah satu bahasa fungsional yang ada, seperti Skema, Haskell, dll.

Akan lebih baik menggunakan waktu untuk mempelajari sintaksis bahasa-bahasa yang secara langsung mendukung semantik tersebut.

Barry Brown
sumber
-3

Dont Tahu tentang C. Ada beberapa fitur fungsional di Objective-C, GCC pada OSX juga mendukung beberapa fitur, namun saya akan merekomendasikan untuk mulai menggunakan bahasa fungsional, ada banyak yang disebutkan di atas. Saya pribadi memulai dengan skema, ada beberapa buku bagus seperti The Little Schemer yang dapat membantu Anda melakukannya.

Pisau
sumber