Có những công cụ nào để lập trình chức năng trong C?


153

Gần đây tôi đã suy nghĩ rất nhiều về cách thực hiện lập trình chức năng trong C ( không phải C ++). Rõ ràng, C là một ngôn ngữ thủ tục và không thực sự hỗ trợ lập trình chức năng nguyên bản.

Có bất kỳ phần mở rộng trình biên dịch / ngôn ngữ nào thêm một số cấu trúc lập trình chức năng vào ngôn ngữ không? GCC cung cấp các hàm lồng nhau như một phần mở rộng ngôn ngữ; các hàm lồng nhau có thể truy cập các biến từ khung ngăn xếp cha, nhưng đây vẫn là một khoảng cách xa so với các bao đóng trưởng thành.

Ví dụ, một điều mà tôi nghĩ có thể thực sự hữu ích trong C là bất cứ nơi nào có con trỏ hàm được mong đợi, bạn có thể truyền biểu thức lambda, tạo một bao đóng để phân rã thành con trỏ hàm. C ++ 0x sẽ bao gồm các biểu thức lambda (mà tôi nghĩ là tuyệt vời); tuy nhiên, tôi đang tìm kiếm các công cụ áp dụng cho C.

[Chỉnh sửa] Để làm rõ, tôi không cố gắng giải quyết một vấn đề cụ thể trong C sẽ phù hợp hơn với lập trình chức năng; Tôi chỉ tò mò về những công cụ hiện có nếu tôi muốn làm như vậy.


1
Thay vì tìm kiếm các bản hack và tiện ích mở rộng không di động để cố gắng biến C thành thứ gì đó không phải, tại sao bạn không sử dụng ngôn ngữ cung cấp chức năng mà bạn đang tìm kiếm?
Robert Gamble

Xem thêm câu hỏi liên quan: < stackoverflow.com/questions/24995/ Khăn >
Andy Brice

@AndyBrice Câu hỏi đó là về một ngôn ngữ khác và thực sự không có nghĩa gì cả (C ++ không có "hệ sinh thái" theo nghĩa tương tự như.
Kyle Strand

Câu trả lời:


40

FFCALL cho phép bạn xây dựng các bao đóng trong C - callback = alloc_callback(&function, data)trả về một con trỏ hàm callback(arg1, ...)tương đương với gọi function(data, arg1, ...). Bạn sẽ phải xử lý bộ sưu tập rác bằng tay, mặc dù.

Liên quan, các khối đã được thêm vào ngã ba GCC của Apple; chúng không phải là con trỏ hàm, nhưng chúng cho phép bạn vượt qua lambdas trong khi tránh sự cần thiết phải xây dựng và lưu trữ miễn phí cho các biến bị bắt bằng tay (một cách hiệu quả, một số sao chép và đếm tham chiếu xảy ra, ẩn đằng sau một số thư viện thời gian và cú pháp cú pháp).


89

Bạn có thể sử dụng các hàm lồng nhau của GCC để mô phỏng các biểu thức lambda, trên thực tế, tôi có một macro để làm điều đó cho tôi:

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

Sử dụng như thế này:

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

12
Điều này thực sự mát mẻ. Có vẻ như __fn__chỉ là tên tùy ý cho hàm được xác định trong khối ({... }), không phải là một số phần mở rộng GCC hoặc macro được xác định trước? Việc lựa chọn tên cho __fn__(trông rất giống định nghĩa GCC) thực sự khiến tôi phải vò đầu bứt tai và tìm kiếm tài liệu GCC không có hiệu quả tốt.
FooF

1
Đáng buồn thay, điều này không hoạt động trong thực tế: nếu bạn muốn đóng cửa để nắm bắt bất cứ điều gì, nó đòi hỏi một ngăn xếp thực thi, đó là một thực tiễn bảo mật khủng khiếp. Cá nhân, tôi không nghĩ rằng nó hữu ích ở tất cả.
Demi

Nó không hữu ích từ xa, nhưng nó rất vui. Đó là lý do tại sao câu trả lời khác được chấp nhận.
Joe D

65

Lập trình hàm không phải là về lambdas, nó là tất cả về các hàm thuần túy. Vì vậy, sau đây quảng bá rộng rãi phong cách chức năng:

  1. Chỉ sử dụng đối số chức năng, không sử dụng trạng thái toàn cầu.

  2. Giảm thiểu tác dụng phụ tức là printf hoặc bất kỳ IO nào. Trả về dữ liệu mô tả IO có thể được thực thi thay vì gây ra tác dụng phụ trực tiếp trong tất cả các chức năng.

Điều này có thể đạt được ở đồng bằng c, không cần phép thuật.


5
Tôi nghĩ rằng bạn đã đưa quan điểm cực đoan về lập trình chức năng đến mức vô lý. Điều gì tốt là một đặc điểm kỹ thuật thuần túy, ví dụ map, nếu một người không có phương tiện để truyền một chức năng cho nó?
Jonathan Leonard

27
Tôi nghĩ cách tiếp cận này thực dụng hơn nhiều so với sử dụng macro để tạo ra một ngôn ngữ chức năng bên trong c. Sử dụng các hàm thuần túy một cách thích hợp có nhiều khả năng cải thiện một hệ thống hơn là sử dụng bản đồ thay vì cho các vòng lặp.
Andy đến

6
Chắc chắn bạn biết rằng bản đồ chỉ là điểm khởi đầu. Không có các hàm hạng nhất, bất kỳ chương trình nào (ngay cả khi tất cả các hàm của nó là 'thuần') sẽ có thứ tự thấp hơn (theo nghĩa lý thuyết thông tin) so với các hàm hạng nhất. Theo quan điểm của tôi, phong cách khai báo này chỉ có thể với các hàm hạng nhất là lợi ích chính của FP. Tôi nghĩ rằng bạn đang làm cho ai đó một sự bất đồng để ám chỉ rằng tính dài dòng do thiếu các chức năng hạng nhất là tất cả những gì FP phải cung cấp. Cần lưu ý rằng trong lịch sử thuật ngữ FP ngụ ý funcs hạng nhất nhiều hơn độ tinh khiết.
Jonathan Leonard

PS Nhận xét trước đó là từ điện thoại di động-- ngữ pháp bất quy tắc không cố ý.
Jonathan Leonard

1
Câu trả lời của Andy Tills hiển thị một cái nhìn rất thực tế về mọi thứ. Đi "Pure" trong C không yêu cầu bất cứ điều gì lạ mắt và mang lại lợi ích lớn. Chức năng hạng nhất, trong các tài khoản so sánh với "cuộc sống thoải mái". Đó là thuận tiện, nhưng áp dụng một phong cách lập trình thuần túy là thực tế. Tôi tự hỏi tại sao không ai thảo luận về các cuộc gọi đuôi liên quan đến tất cả điều này. Những người muốn chức năng hạng nhất cũng nên yêu cầu các cuộc gọi đuôi.
BitTickler

17

Cuốn sách của Hartel & Muller, Functional C , ngày nay có thể được tìm thấy (2012-01 / 02) tại: http://eprints.eemcs.utwente.nl/1077/ (có liên kết đến phiên bản PDF).


2
Không có bất kỳ nỗ lực nào trong cuốn sách này (liên quan đến câu hỏi).
Eugene Tolmachev

4
Có vẻ như bạn khá đúng. Thật vậy, lời nói đầu cho biết mục đích của cuốn sách là dạy lập trình mệnh lệnh sau khi học sinh đã làm quen với lập trình chức năng. FYI, đây là câu trả lời đầu tiên của tôi trong SO và nó chỉ được viết dưới dạng câu trả lời vì tôi không yêu cầu danh tiếng để bình luận một câu trả lời trước đó với liên kết thối. Tôi luôn tự hỏi tại sao mọi người giữ +1 câu trả lời này ... Xin đừng làm vậy! :-)
FooF

1
Có lẽ cuốn sách nên được gọi là Lập trình sau chức năng tốt hơn (trước hết là vì "mệnh lệnh C" không có vẻ gợi cảm, thứ hai bởi vì nó giả định sự quen thuộc với mô hình lập trình chức năng, thứ ba là một cách chơi chữ vì mô hình bắt buộc có vẻ như sự suy giảm của các tiêu chuẩn và đúng chức năng, và có lẽ là thứ tư để đề cập đến khái niệm lập trình hậu hiện đại của Larry Wall - mặc dù cuốn sách này được viết trước bài báo / bài thuyết trình của Larry Wall).
FooF

Và đây là một câu trả lời trùng lặp
PhilT

8

Điều kiện tiên quyết cho phong cách lập trình chức năng là một chức năng hạng nhất. Nó có thể được mô phỏng trong C xách tay nếu bạn chịu đựng tiếp theo:

  • quản lý thủ công các ràng buộc phạm vi từ vựng, còn gọi là đóng cửa.
  • quản lý thủ công các biến số trọn đời.
  • cú pháp thay thế của ứng dụng chức năng / cuộc gọi.
/* 
 * 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;

thời gian chạy cho mã như vậy có thể nhỏ như dưới đây

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

Về bản chất, chúng tôi bắt chước hàm hạng nhất với các bao đóng được biểu diễn dưới dạng cặp hàm / đối số cộng với các macro. Mã hoàn chỉnh có thể được tìm thấy ở đây .


7

Điều chính mà tôi nghĩ đến là việc sử dụng các trình tạo mã. Bạn có sẵn sàng lập trình bằng một ngôn ngữ khác cung cấp chương trình chức năng và sau đó tạo mã C từ đó không?

Nếu đó không phải là một lựa chọn hấp dẫn, thì bạn có thể lạm dụng CPP để có được một phần của cách đó. Hệ thống vĩ mô sẽ cho phép bạn mô phỏng một số ý tưởng lập trình chức năng. Tôi đã nghe nói rằng gcc được thực hiện theo cách này nhưng tôi chưa bao giờ kiểm tra.

Tất nhiên C có thể vượt qua các hàm xung quanh bằng cách sử dụng các con trỏ hàm, các vấn đề chính là thiếu các bao đóng và hệ thống loại có xu hướng cản trở. Bạn có thể khám phá các hệ thống vĩ mô mạnh hơn CPP như M4. Tôi đoán cuối cùng, điều tôi đề nghị là C thực sự không hoàn thành nhiệm vụ mà không cần nỗ lực nhiều nhưng bạn có thể mở rộng C để hoàn thành nhiệm vụ. Tiện ích mở rộng đó sẽ trông giống C nhất nếu bạn sử dụng CPP hoặc bạn có thể đi đến đầu kia của phổ và tạo mã C từ một số ngôn ngữ khác.


Đây là câu trả lời trung thực nhất. Cố gắng viết C một cách có chức năng là để chống lại chính thiết kế và cấu trúc ngôn ngữ.
josiah

5

Nếu bạn muốn thực hiện việc đóng cửa, bạn sẽ phải thực hiện giao tiếp với ngôn ngữ lắp ráp và quản lý / hoán đổi ngăn xếp. Không đề xuất chống lại nó, chỉ nói rằng đó là những gì bạn sẽ phải làm.

Tuy nhiên, không chắc chắn cách bạn sẽ xử lý các chức năng ẩn danh trong C. Trên máy von Neumann, bạn có thể thực hiện các chức năng ẩn danh trong asm.



2

Các ngôn ngữ Felix biên dịch C ++. Có lẽ đó có thể là một bước đệm, nếu bạn không nhớ C ++.


9
⁻¹ bởi vì α) Tác giả đã đề cập rõ ràng «không phải C ++», β) C ++ được tạo bởi trình biên dịch sẽ không phải là «C ++ có thể đọc được của con người». ) Chuẩn C ++ hỗ trợ lập trình chức năng, không cần sử dụng trình biên dịch từ ngôn ngữ này sang ngôn ngữ khác.
Hi-Angel

Khi bạn đưa ra một ngôn ngữ chức năng bằng các macro hoặc tương tự, bạn sẽ tự động ám chỉ rằng bạn không quan tâm lắm đến C. Câu trả lời này hợp lệ và ổn vì ngay cả C ++ cũng bắt đầu như một bữa tiệc macro trên C. Nếu bạn đi xuống con đường đó đủ xa, bạn sẽ kết thúc với một ngôn ngữ mới. Sau đó, nó chỉ đi xuống một cuộc thảo luận kết thúc.
BitTickler

1

Cũng có một vài ngôn ngữ lập trình được viết bằng C. Và một số trong số chúng hỗ trợ các chức năng như công dân hạng nhất, các ngôn ngữ trong khu vực đó là ecl (bắt buộc phổ biến IIRC), Gnu Smalltalk (gst) (Smalltalk có các khối), sau đó có các thư viện cho "đóng cửa" ví dụ trong glib2 http://library.gnome.org/devel/gobject/unstable/chapter-signal.html#closure đó ít nhất có gần lập trình chức năng. Vì vậy, có thể sử dụng một số trong những triển khai đó để làm lập trình chức năng có thể là một lựa chọn.

Chà hoặc bạn có thể đi học Ocaml, Haskell, Mozart / Oz hoặc tương tự ;-)

Trân trọng


Bạn có phiền khi nói với tôi điều gì quá tệ khi sử dụng các thư viện ít nhất cũng hỗ trợ cho phong cách lập trình của FP không?
Friedrich

1

Cách tôi bắt đầu thực hiện lập trình chức năng trong C là viết một trình thông dịch ngôn ngữ chức năng trong C. Tôi đặt tên là Fexl, viết tắt của "Ngôn ngữ loại bỏ chức năng".

Trình thông dịch rất nhỏ, biên dịch xuống 68K trên hệ thống của tôi với -O3 được bật. Nó cũng không phải là một món đồ chơi - Tôi đang sử dụng nó cho tất cả các mã sản xuất mới mà tôi viết cho doanh nghiệp của mình (kế toán dựa trên web cho các mối quan hệ đối tác đầu tư.)

Bây giờ tôi chỉ viết mã C để (1) thêm một hàm dựng sẵn gọi một thói quen hệ thống (ví dụ: fork, exec, setrlimit, v.v.) hoặc (2) tối ưu hóa một chức năng có thể được viết trong Fexl (ví dụ: tìm kiếm cho một chuỗi con).

Cơ chế mô-đun dựa trên khái niệm "bối cảnh". Một bối cảnh là một hàm (được viết bằng Fexl) để ánh xạ một biểu tượng theo định nghĩa của nó. Khi bạn đọc tệp Fexl, bạn có thể giải quyết nó với bất kỳ ngữ cảnh nào bạn muốn. Điều này cho phép bạn tạo các môi trường tùy chỉnh hoặc chạy mã trong một "hộp cát" bị hạn chế.

http://fexl.com


-3

Điều gì về C mà bạn muốn thực hiện chức năng, cú pháp hoặc ngữ nghĩa? Các ngữ nghĩa của lập trình chức năng chắc chắn có thể được thêm vào trình biên dịch C, nhưng khi bạn hoàn thành, về cơ bản bạn có tương đương với một trong các ngôn ngữ chức năng hiện có, như Scheme, Haskell, v.v.

Sẽ là sử dụng thời gian tốt hơn để chỉ học cú pháp của các ngôn ngữ hỗ trợ trực tiếp các ngữ nghĩa đó.


-3

Không biết về C. Có một số tính năng chức năng trong Objective-C, GCC trên OSX cũng hỗ trợ một số tính năng, tuy nhiên tôi lại khuyên bạn nên bắt đầu sử dụng ngôn ngữ chức năng, có rất nhiều đề cập ở trên. Cá nhân tôi bắt đầu với chương trình, có một số cuốn sách tuyệt vời như The Little Schemer có thể giúp bạn làm điều đó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.