В последнее время я много думал о том, как заняться функциональным программированием на C (нет C++). Очевидно, что C - это процедурный язык, который изначально не поддерживает функциональное программирование.
Существуют ли какие-либо расширения компилятора / языка, которые добавляют в язык некоторые конструкции функционального программирования? GCC предоставляет вложенные функции как расширение языка; вложенные функции могут обращаться к переменным из кадра родительского стека, но до зрелого замыкания еще далеко.
Например, одна вещь, которая, как мне кажется, может быть действительно полезной в C, - это то, что везде, где ожидается указатель на функцию, вы можете передать лямбда-выражение, создавая замыкание, которое распадается на указатель на функцию. C++ 0x будет включать лямбда-выражения (что, на мой взгляд, потрясающе); однако я ищу инструменты, применимые к прямому C.
[Edit] Чтобы уточнить, я не пытаюсь решить конкретную проблему на C, которая больше подходит для функционального программирования; Мне просто любопытно, какие инструменты существуют, если я хочу это сделать.
@AndyBrice Этот вопрос касается другого языка и на самом деле не имеет смысла (C++ не имеет «экосистемы» в том же смысле, что и.
Вместо того, чтобы искать хаки и непереносимые расширения, чтобы попытаться превратить C во что-то, чем он не является, почему бы вам просто не использовать язык, который обеспечивает функциональность, которую вы ищете?





Главное, что приходит в голову, - это использование генераторов кода. Хотели бы вы программировать на другом языке, обеспечивающем функциональное программирование, а затем сгенерировать из него код C?
Если это непривлекательный вариант, то вы можете злоупотребить CPP, чтобы получить часть пути. Макросистема должна позволить вам подражать некоторым идеям функционального программирования. Я слышал, что gcc реализован таким образом, но никогда не проверял.
C, конечно, может передавать функции, используя указатели на функции, основные проблемы заключаются в отсутствии замыканий, а система типов имеет тенденцию мешать. Вы можете изучить более мощные макросистемы, чем CPP, такие как M4. Я предполагаю, что в конечном итоге я предлагаю, чтобы истинный C не справился с задачей без больших усилий, но вы могли бы расширить C, чтобы он соответствовал задаче. Это расширение будет больше всего похоже на C, если вы используете CPP, или вы можете перейти на другой конец спектра и сгенерировать код C на каком-то другом языке.
Это самый честный ответ. Попытка написать C функционально - это борьба с самим дизайном и структурой языка.
Ну, довольно много языков программирования написано на C.И некоторые из них поддерживают функции как первоклассные граждане, языки в этой области - это ecl (встраиваемый общий lisp IIRC), Gnu Smalltalk (gst) (Smalltalk имеет блоки), а также есть библиотеки. для "замыканий", например, в glib2 http://library.gnome.org/devel/gobject/unstable/chapter-signal.html#closure который хотя бы приблизился к функциональному программированию. Так что, возможно, использование некоторых из этих реализаций для функционального программирования может быть вариантом.
Ну или вы можете пойти изучать Ocaml, Haskell, Mozart / Oz или тому подобное ;-)
С уважением
Не могли бы вы сказать мне, что такого ужасного в использовании библиотек, которые хотя бы хорошо поддерживают стиль программирования FP?
Что в C вы хотите сделать функциональным, синтаксис или семантику? Семантика функционального программирования, безусловно, может быть добавлена в компилятор C, но к тому времени, когда вы закончите, у вас будет практически эквивалент одного из существующих функциональных языков, таких как Scheme, Haskell и т. д.
Было бы лучше использовать время, чтобы просто изучить синтаксис тех языков, которые напрямую поддерживают эту семантику.
FFCALL позволяет вам создавать замыкания в C - callback = alloc_callback(&function, data) возвращает указатель на функцию, так что callback(arg1, ...) эквивалентен вызову function(data, arg1, ...). Однако вам придется обрабатывать сборку мусора вручную.
Соответственно, блоки был добавлен в форк GCC от Apple; они не указатели на функции, но они позволяют передавать лямбды, избегая при этом необходимости создавать и освобождать хранилище для захваченных переменных вручную (фактически, происходит некоторое копирование и подсчет ссылок, скрытые за некоторым синтаксическим сахаром и библиотеками времени выполнения).
Посмотрите книгу Хартеля и Мюллера, Функциональный C
http://www.ub.utwente.nl/webdocs/ctit/1/00000084.pdf
http://www.cs.bris.ac.uk/~henkm/f2c/index.html
Если вы хотите реализовать замыкания, вам придется освоиться с языком ассемблера и переключением / управлением стеками. Не рекомендую против этого, просто говорю, что это то, что вам нужно сделать.
Не уверен, как вы будете обрабатывать анонимные функции в C. Однако на машине фон Неймана вы могли бы выполнять анонимные функции в asm.
Не знаю о C. В Objective-C есть некоторые функциональные возможности, GCC на OSX также поддерживает некоторые функции, однако я бы снова рекомендовал начать использовать функциональный язык, их много, упомянутого выше. Я лично начал со схемы, есть несколько отличных книг, таких как «Маленький программист», которые могут вам в этом помочь.
Вы можете использовать вложенные функции 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; });
Это действительно круто. Кажется, __fn__ - это просто произвольное имя функции, определенной в блоке ({ ... }), а не какое-то расширение GCC или предопределенный макрос? Выбор имени для __fn__ (очень похожего на определение GCC) действительно заставил меня почесать затылок и поискать в документации GCC без хорошего эффекта.
К сожалению, на практике это не работает: если вы хотите, чтобы закрытие что-то захватило, для этого потребуется исполняемый стек, что является ужасной практикой безопасности. Лично я не думаю, что это вообще полезно.
Это бесполезно, но весело. Поэтому принимается другой ответ.
Это весело, но бесполезно
Язык Феликса компилируется в C++. Может быть, это может быть ступенькой, если вы не против C++.
⁻¹ потому что α) Автор прямо упомянул «не C++», β) C++, созданный компилятором, не будет «C++ для чтения человеком». γ) Стандарт C++ поддерживает функциональное программирование, нет необходимости использовать компилятор с одного языка на другой.
Как только вы придумываете функциональный язык с помощью макросов или чего-то подобного, вы автоматически подразумеваете, что вас не слишком заботит C. Пройдите по этой дороге достаточно далеко, и вы получите новый язык. Тогда это только доходит до обсуждения серверной части.
Книгу Хартеля и Мюллера, Функциональный C, в настоящее время (2012-01-02) можно найти по адресу: http://eprints.eemcs.utwente.nl/1077/ (есть ссылка на версию в формате PDF).
В этой книге нет попытки придумать что-либо функциональное (что касается вопроса).
Похоже, вы совершенно правы. Действительно, в предисловии говорится, что цель книги - обучить императивному программированию после того, как студент уже ознакомился с функциональным программированием. К вашему сведению, это был мой первый ответ в SO, и он был написан как ответ только потому, что мне не требовалась репутация, чтобы прокомментировать предыдущий ответ с гнилостной ссылкой. Я всегда удивляюсь, почему люди добавляют +1 к этому ответу ... Пожалуйста, не делайте этого! :-)
Возможно, книгу лучше назвать постфункциональным программированием (во-первых, потому что «Императивный C» не звучит привлекательно, во-вторых, потому что он предполагает знакомство с парадигмой функционального программирования, в-третьих, как каламбур, потому что императивная парадигма кажется упадком стандартов и правильная функциональность и, возможно, в-четвертых, чтобы отсылать к концепции постмодернистского программирования Ларри Уолла - хотя эта книга была написана до статьи / презентации Ларри Уолла).
И это дублированный ответ
Функциональное программирование - это не лямбды, а чистые функции. Таким образом, функциональный стиль широко продвигают:
Используйте только аргументы функции, не используйте глобальное состояние.
Сведите к минимуму побочные эффекты, например printf или любой ввод-вывод. Возвращает данные, описывающие ввод-вывод, который может выполняться вместо того, чтобы вызывать побочные эффекты непосредственно во всех функциях.
Это может быть достигнуто с помощью простого c, без магии.
Я думаю, вы довели этот крайний взгляд на функциональное программирование до абсурда. Что хорошего в чистой спецификации, например, map, если у кого-то нет средств для передачи ей функции?
Я думаю, что этот подход гораздо более прагматичен, чем использование макросов для создания функционального языка внутри c. Правильное использование чистых функций с большей вероятностью улучшит систему, чем использование map вместо циклов for.
Наверняка вы знаете, что карта - это только отправная точка. Без функций первого класса любая программа (даже если все ее функции «чистые») будет иметь более низкий порядок (в смысле теории информации), чем ее эквивалент с функциями первого класса. На мой взгляд, именно этот декларативный стиль, возможный только с функциями первого класса, является основным преимуществом FP. Я думаю, вы оказываете кому-то медвежью услугу, подразумевая, что многословие, возникающее из-за отсутствия первоклассных функций, - это все, что может предложить FP. Следует отметить, что исторически термин FP подразумевал больше первоклассных функций, чем чистоту.
P.S. Предыдущий комментарий был с мобильного телефона - неправильная грамматика не была намеренной.
Я не против первоклассных функций, пользуюсь ими каждый день и без них не обойтись. Впрочем, никогда не было, удачи в этом.
Разве это не называется «чистым функциональным программированием»? Я бы сказал, что функциональное программирование - это представление о программе как о рекурсивной композиции функций, и чистота ей ортогональна. Purity помогает вам (или компьютеру) рассуждать об этом, для подтверждения правильности и для оптимизации. Я думаю, что первоклассные функции необходимы, чтобы делать полезные вещи или, по крайней мере, делать их хорошо, но этого недостаточно.
Ответ Энди Тиллса отражает очень прагматичный взгляд на вещи. Переход на «чистый» в языке С не требует ничего особенного и приносит большую пользу. Первоклассные функции по сравнению с «комфортным проживанием». Это удобно, но использование чистого стиля программирования практично. Интересно, почему никто не обсуждает хвостовые вызовы в отношении всего этого. Те, кому нужны первоклассные функции, также должны попросить хвостовые вызовы.
Предпосылкой для функционального стиля программирования является первоклассная функция. Это можно было бы смоделировать на портативном C, если вы допустите следующее:
/*
* 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 });
По сути, мы имитируем функцию первого класса с замыканиями, представленными как пара функция / аргумент плюс набор макросов. Полный код можно найти здесь.
Способ, которым я занимался функциональным программированием на C, заключался в написании интерпретатора функционального языка на C. Я назвал его Fexl, что сокращенно от «Function EXpression Language».
Интерпретатор очень маленький, компилируется до 68 КБ в моей системе с включенным -O3. Это тоже не игрушка - я использую ее для всего нового производственного кода, который пишу для своего бизнеса (веб-учет для инвестиционных партнерств).
Теперь я пишу код на C только для того, чтобы (1) добавить встроенную функцию, вызывающую системную процедуру (например, fork, exec, setrlimit и т. д.), Или (2) оптимизировать функцию, которая в противном случае могла бы быть написана на Fexl (например, search для подстроки).
Механизм модуля основан на концепции «контекста». Контекст - это функция (написанная на Fexl), которая сопоставляет символ с его определением. Когда вы читаете файл Fexl, вы можете разрешить его с любым контекстом, который вам нравится. Это позволяет вам создавать собственные среды или запускать код в ограниченной «песочнице».
См. Также связанный вопрос: <stackoverflow.com/questions/24995/…>