Які засоби існують для функціонального програмування на C?


153

Останнім часом я багато думав про те, як займатися функціональним програмуванням на C (а не на C ++). Очевидно, що C є процедурною мовою і насправді не підтримує функціональне програмування.

Чи є компілятори / розширення мови, які додають до мови деякі функціональні конструктивні програми? GCC надає вкладені функції як розширення мови; вкладені функції можуть отримати доступ до змінних з батьківського кадру стека, але це ще далеко від зрілих закриттів.

Наприклад, одна річ, яка, на мою думку, може бути дуже корисною для C - це те, що в будь-якому місці, де очікується функціональний покажчик, ви зможете передати лямбда-вираз, створивши закриття, яке перетворюється на функціональний покажчик. C ++ 0x буде включати лямбда-вирази (що, на мою думку, є дивним); однак я шукаю інструменти, застосовні до прямої C.

[Редагувати] Для уточнення, я не намагаюся вирішити конкретну проблему в C, яка була б більше підходить для функціонального програмування; Мені просто цікаво, які інструменти є там, якби я хотів це зробити.


1
Замість того, щоб шукати хаки та не портативні розширення, щоб спробувати перетворити C на щось не так, чому б вам просто не використовувати мову, яка забезпечує функціонал, який ви шукаєте?
Роберт Гембл

Дивіться також пов’язане запитання: < stackoverflow.com/questions/24995/… >
Енді Бріс

@AndyBrice Це питання стосується іншої мови і насправді не має сенсу (C ++ не має "екосистеми" в тому ж сенсі, як.
Kyle Strand,

Відповіді:


40

FFCALL дозволяє будувати закриття в C - callback = alloc_callback(&function, data)повертає функціональний покажчик таким, який callback(arg1, ...)еквівалентний виклику function(data, arg1, ...). Однак вам доведеться обробляти сміття вручну.

Аналогічно, блоки були додані до вилки Apple GCC; вони не функціонують вказівниками, але вони дозволяють вам обходити лямбда, уникаючи необхідності створювати та звільняти сховище для захоплених змінних вручну (фактично, відбувається деяке копіювання та підрахунок посилань, приховане за деякими синтаксичними бібліотеками цукру та часу виконання).


89

Ви можете використовувати вкладені функції GCC для імітації лямбда-виразів. Насправді у мене є макрос, щоб зробити це для мене:

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

Використовуйте так:

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

12
Це справді класно. Здається, __fn__це просто довільна назва функції, визначеної в блоці ({... }), а не якесь розширення GCC або попередньо визначений макрос? Вибір імені __fn__(дуже схожого на визначення GCC) насправді змусив почухати голову та шукати документацію GCC, щоб не мати хорошого ефекту.
FooF

1
На жаль, це не працює на практиці: якщо ви хочете, щоб закриття захопило що-небудь, для цього потрібен виконуваний стек, що є жахливою практикою безпеки. Особисто я не думаю, що це взагалі корисно.
Демі

Це не віддалено корисно, але це весело. Ось чому інша відповідь прийнята.
Джо D

65

Функціональне програмування - це не лямбда, це все про чисті функції. Отже, наступне широке просування функціонального стилю:

  1. Використовуйте лише аргументи функції, не використовуйте глобальний стан.

  2. Мінімізуйте побічні ефекти, тобто printf або будь-який IO. Поверніть дані, що описують IO, який може бути виконаний, а не викликати побічні ефекти безпосередньо у всіх функціях.

Цього можна досягти в простому с, не потрібно магії.


5
Я думаю, ви сприйняли цей крайній погляд на функціональне програмування до абсурду. Яка користь - чиста специфікація, наприклад, mapякщо людина не має можливості передати їй функцію?
Джонатан Леонард

27
Я думаю, що цей підхід є набагато більш прагматичним, ніж використання макросів для створення функціональної мови всередині c. Використання чистих функцій належним чином покращить систему, ніж використання карти замість циклів.
Енді до

6
Звичайно, ви знаєте, що карта - лише відправна точка. Без першокласних функцій будь-яка програма (навіть якщо всі її функції "чисті") буде нижчого порядку (в сенсі теорії інформації), ніж її еквівалент функціям першого класу. На мою думку, саме цей декларативний стиль можливий лише за допомогою першокласних функцій, що є основною перевагою FP. Я думаю, ви робите комусь недобре, щоб натякнути на те, що багатослівність, яка виникає через відсутність першокласних функцій, - це все, що може запропонувати FP. Слід зазначити, що історично термін FP мав на увазі першокласні функціонування більше, ніж чистота.
Джонатан Леонард

PS Попередній коментар був з мобільних-- неправильна граматика не була навмисною.
Джонатан Леонард

1
Відповідь Енді Тіллса відображає дуже прагматичний погляд на речі. Їхати «чисто» в С не вимагає нічого фантазії і приносить велику користь. Функції першого класу, порівняно, пояснюються "комфортом життя". Це зручно, але прийняти чистий стиль програмування практично. Цікаво, чому ніхто не обговорює хвостові дзвінки стосовно всього цього. Бажаючі функції першого класу також повинні просити дзвінки в хвіст.
BitTickler

17

Книгу Hartel & Muller, Функціонал C , сьогодні (2012-01-02) можна знайти за адресою: http://eprints.eemcs.utwente.nl/1077/ (є посилання на версію PDF).


2
У цій книзі немає спроб нічого функціонального (як це стосується питання).
Євген Толмачев

4
Здається, ви абсолютно праві. Дійсно, в передмові зазначається, що мета книги - навчити імперативному програмуванню після того, як студент вже ознайомився з функціональним програмуванням. FYI, це була моя перша відповідь в SO, і вона була написана як відповідь лише тому, що я не вимагав репутації, щоб коментувати попередню відповідь за гнилим посиланням. Мені завжди цікаво, чому люди тримають +1 цю відповідь ... Будь ласка, не робіть цього! :-)
FooF

1
Можливо, книгу слід було б краще назвати Постфункціональним програмуванням (по-перше, тому, що "Imperative C" не звучить сексуально, по-друге, тому, що передбачає знайомство з функціональною парадигмою програмування, по-третє, як каламбур, оскільки імперативна парадигма здається зниженням стандартів і правильний функціонал, і, можливо, четвертий - стосовно поняття Ларрі Уолла про постмодерністське програмування - хоча ця книга була написана до статті / презентації Ларрі Уолла).
FooF

І це повторювана відповідь
PhilT

8

Необхідною умовою функціонального стилю програмування є функція першого класу. Це може бути імітовано на портативному С, якщо ви терпите наступне:

  • ручне управління лексичними прив'язками сфери, відомі також як закриття.
  • ручне управління життєвими функціями змінних.
  • альтернативний синтаксис функції застосування / виклик функції.
/* 
 * 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;

Час виконання такого коду може бути таким же невеликим, як один нижче

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

По суті, ми імітуємо функцію першого класу із закриттями, представленими у вигляді пари функцій / аргументів плюс купу макрозів. Повний код можна знайти тут .


7

Головне, що спадає на думку, - це використання генераторів коду. Чи хотіли б ви програмувати на іншій мові, яка забезпечує функціональне програмування, а потім генерувати з цього коду С?

Якщо це не привабливий варіант, ви можете зловживати CPP, щоб потрапити на частину шляху. Макросистема повинна дозволяти вам наслідувати деякі ідеї функціонального програмування. Я чув, як кажуть, що gcc реалізований таким чином, але я ніколи не перевіряв.

Звичайно, можна передати функції навколо функціональних покажчиків, основні проблеми - відсутність закриттів і система типу, як правило, перешкоджає. Ви можете досліджувати більш потужні макросистеми, ніж CPP, такі як M4. Я думаю, в кінцевому рахунку, те, що я припускаю, полягає в тому, що справжній C не вирішує завдання без великих зусиль, але ви можете розширити C, щоб зробити його до виконання завдання. Це розширення буде найбільше схожим на C, якщо ви використовуєте CPP або ви можете перейти на інший кінець спектру та генерувати код C з іншої мови.


Це найчесніша відповідь. Намагатися писати С функціонально - це боротися проти мовної конструкції та самої структури.
josiah

5

Якщо ви хочете реалізувати закриття, вам доведеться перейнятися мовою складання та заміною / керуванням стеками. Не рекомендуючи проти цього, просто кажу, що це вам доведеться зробити.

Не впевнений, як ви будете обробляти анонімні функції в C. На машині фон Неймана ви могли б виконувати анонімні функції в asm.



2

Мова Фелікса компілюється на C ++. Можливо, це може бути кроком, якщо ви не заперечуєте проти C ++.


9
⁻¹ тому що α) Автор явно згадував «не C ++», β) C ++, створений компілятором, не був би «читабельним для людини C ++». γ) Стандарт C ++ підтримує функціональне програмування, не потрібно використовувати компілятор з однієї мови на іншу.
Привіт-Ангел

Після того, як ви придумали функціональну мову за допомогою макросів або подібних, ви автоматично натякаєте на те, що ви не так сильно піклуєтесь про C. Ця відповідь є дійсною і нормальною, оскільки навіть C ++ розпочався як макро-вечірка поверх C. Якщо ви піти цією дорогою досить далеко, ви закінчитеся з новою мовою. Тоді це лише зводиться до дискусії заднього кінця.
BitTickler

1

Ну досить багато мов програмування написано на C. І деякі з них підтримують функції як громадян першого класу, мови в цій області - це ecl (embbedabble common lisp IIRC), Gnu Smalltalk (gst) (Smalltalk має блоки), то є бібліотеки для "закриття", наприклад, у glib2 http://library.gnome.org/devel/gobject/unstable/chapter-signal.html#closure, який принаймні наблизився до функціонального програмування. Тому можливо використання деяких із цих реалізацій для функціонального програмування.

Ну або ви можете піти вивчати Ocaml, Haskell, Mozart / Oz тощо ;-)

З повагою


Ви б не хотіли сказати мені, що так страшного поганого у використанні бібліотек, які принаймні підтримують стиль програмування FP?
Фрідріх

1

Шлях, що я займався функціональним програмуванням на C, полягав у написанні функціонального інтерпретатора мови в C. Я назвав його Fexl, що скорочено "Function EXpression Language".

Інтерпретатора дуже мало, в моїй системі ввімкнено до 68 К з включеним -O3. Це не іграшка - я використовую її для всього нового виробничого коду, який я пишу для свого бізнесу (веб-облік інвестиційних партнерств.)

Тепер я записую код C лише до (1), щоб додати вбудовану функцію, яка викликає системну процедуру (наприклад, fork, exec, setrlimit тощо), або (2) оптимізувати функцію, яка в іншому випадку могла бути записана у Fexl (наприклад, пошук для підстрочки).

Модульний модуль заснований на концепції "контексту". Контекст - це функція (написана у Fexl), яка відображає символ до його визначення. Коли ви читаєте файл Fexl, ви можете вирішити його в будь-якому контексті. Це дозволяє створювати власні середовища або запускати код у обмеженій "пісочниці".

http://fexl.com


-3

Що з C, яке ви хочете зробити функціональним, синтаксисом чи семантикою? Семантика функціонального програмування, безумовно, може бути додана до компілятора С, але до того моменту, як ви це зробили, ви по суті мали б еквівалент однієї з існуючих функціональних мов, таких як схема, Haskell тощо.

Було б краще використовувати час, щоб просто вивчити синтаксис тих мов, які безпосередньо підтримують цю семантику.


-3

Не знаю про C. Існують деякі функціональні особливості в Objective-C, хоча GCC на OSX також підтримує деякі функції, однак я знову рекомендую почати використовувати функціональну мову, тут багато згаданих вище. Я особисто почав зі схеми, є кілька чудових книг, таких як Маленький Схемер, які можуть допомогти вам це зробити.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.