Какие инструменты есть для функционального программирования на C?

153

В последнее время я много думал о том, как заниматься функциональным программированием на C ( не на C ++). Очевидно, что C является процедурным языком и на самом деле не поддерживает функциональное программирование изначально.

Существуют ли какие-либо расширения компилятора / языка, которые добавляют некоторые функциональные программные конструкции к языку? GCC предоставляет вложенные функции как расширение языка; Вложенные функции могут обращаться к переменным из родительского стекового фрейма, но до зрелых замыканий это еще далеко.

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

[Править] Чтобы уточнить, я не пытаюсь решить конкретную проблему в C, которая больше подходит для функционального программирования; Мне просто любопытно, какие инструменты существуют, если я хочу это сделать.

Адам Розенфилд
источник
1
Вместо того, чтобы искать взломы и непереносимые расширения, чтобы попытаться превратить C в нечто, чего нельзя, почему бы вам просто не использовать язык, который обеспечивает требуемую функциональность?
Роберт Гэмбл
См. Также связанный вопрос: < stackoverflow.com/questions/24995/… >
Энди Брис
@AndyBrice Этот вопрос касается другого языка и, похоже, на самом деле не имеет смысла (C ++ не имеет «экосистемы» в том же смысле, как.
Кайл Стрэнд,

Ответы:

40

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

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

ephemient
источник
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
К сожалению, на практике это не работает: если вы хотите, чтобы замыкание захватывало что-либо, ему требуется исполняемый стек, что является ужасной практикой безопасности. Лично я не думаю, что это вообще полезно.
Деми
Это не удаленно полезно, но это весело. Вот почему другой ответ принят.
Джо Д
65

Функциональное программирование не о лямбдах, а о чистых функциях. Таким образом, следующие широко продвигают функциональный стиль:

  1. Используйте только аргументы функции, не используйте глобальное состояние.

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

Это может быть достигнуто в простой с, нет необходимости в магии.

Энди Тилль
источник
5
Я думаю, что вы взяли этот крайний взгляд на функциональное программирование до абсурда. Что хорошего в чистой спецификации, например, mapесли у человека нет возможности передать ему функцию?
Джонатан Леонард
27
Я думаю, что этот подход гораздо более прагматичен, чем использование макросов для создания функционального языка внутри c. Правильное использование чистых функций более вероятно улучшит систему, чем использование карты вместо циклов for.
Энди До
6
Конечно, вы знаете, что карта является лишь отправной точкой. Без функций первого класса любая программа (даже если все ее функции являются «чистыми») будет более низкого порядка (в смысле теории информации), чем ее эквивалент с функциями первого класса. На мой взгляд, именно этот декларативный стиль возможен только с первоклассными функциями, что является основным преимуществом FP. Я думаю, что вы оказываете кому-то плохую услугу, подразумевая, что многословие, которое возникает из-за отсутствия первоклассных функций, - это все, что может предложить FP. Следует отметить, что исторически термин FP подразумевал первоклассные функции больше, чем чистоту.
Джонатан Леонард
PS Предыдущий комментарий был с мобильного - неправильная грамматика не была преднамеренной.
Джонатан Леонард
1
Ответ Энди Тиллса показывает очень прагматичный взгляд на вещи. Переход на «Чистый» в Си не требует ничего особенного и приносит большую пользу. Функции первого класса, по сравнению с «комфортной жизни». Это удобно, но практично использовать чистый стиль программирования. Интересно, почему никто не обсуждает хвостовые вызовы в связи со всем этим. Те, кому нужны первоклассные функции, должны также запросить хвостовые вызовы.
BitTickler
17

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

FOOF
источник
2
В этой книге нет попыток сделать что-либо функциональное (в связи с вопросом).
Евгений Толмачев
4
Кажется, вы совершенно правы. Действительно, в предисловии говорится, что цель книги - научить императивному программированию после того, как студент уже ознакомился с функциональным программированием. К вашему сведению, это был мой первый ответ в SO, и он был написан как ответ только потому, что у меня не было репутации, чтобы комментировать предыдущий ответ с гнилой ссылкой. Мне всегда интересно, почему люди держат +1 этот ответ ... Пожалуйста, не делайте этого! :-)
FooF
1
Возможно, книгу лучше было бы назвать Пост-функциональным программированием (во-первых, потому что «Imperative C» не звучит сексуально, во-вторых, потому что предполагает знакомство с парадигмой функционального программирования, в-третьих, как каламбур, потому что императивная парадигма кажется упадком стандартов и правильная функциональность и, возможно, в-четвертых, относится к понятию Ларри Уолла о постмодернистском программировании - хотя эта книга была написана до статьи / презентации Ларри Уолла).
FooF
И это
повторный
8

Необходимым условием для функционального стиля программирования является функция первого класса. Это может быть смоделировано в переносном 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 });

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

Виктор Шепель
источник
7

Главное, что приходит на ум - это использование генераторов кода. Желаете ли вы программировать на другом языке, обеспечивающем функциональное программирование, а затем генерировать код C из этого?

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

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

Джейсон Дагит
источник
Это самый честный ответ. Попытка написать C функционально - это борьба с языковым дизайном и структурой.
Иосия
5

Если вы хотите реализовать замыкания, вам нужно будет познакомиться с языком ассемблера и обменом / управлением стека. Не рекомендую против этого, просто говорю, что это то, что вам придется сделать.

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

Пол Натан
источник
2

Язык Феликса компилируется в C ++. Может быть, это может быть ступенькой, если вы не против C ++.

LennyStackOverflow
источник
9
⁻¹ потому что α) Автор явно упомянул «не C ++», β) C ++, созданный компилятором, не был бы «читабельным C ++». γ) Стандарт C ++ поддерживает функциональное программирование, нет необходимости использовать компилятор с одного языка на другой.
Привет, Ангел,
Как только вы придумали функциональный язык с помощью макросов или тому подобного, вы автоматически подразумеваете, что вам не очень важен C. Это ответ верный и нормальный, потому что даже C ++ начинался как макропакет над C. Если вы идите по этому пути достаточно далеко, и в итоге вы получите новый язык. Тогда это только сводится к внутреннему обсуждению.
BitTickler
1

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

Ну или вы можете пойти изучать Ocaml, Haskell, Моцарта / Oz или тому подобное ;-)

С уважением

Фридрих
источник
Не могли бы вы сказать мне, что такого ужасного в использовании библиотек, которые по крайней мере поддерживают стиль программирования FP?
Фридрих
1

То, как я начал заниматься функциональным программированием на C, заключалось в написании интерпретатора функционального языка на C. Я назвал его Fexl, что сокращенно от «Язык выражения функций».

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

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

Механизм модуля основан на понятии «контекст». Контекст - это функция (написанная на Fexl), которая сопоставляет символ с его определением. Когда вы читаете файл Fexl, вы можете разрешить его в любом контексте. Это позволяет создавать пользовательские среды или запускать код в ограниченной «песочнице».

http://fexl.com

Патрик Чкорефф
источник
-3

Что в Си вы хотите сделать функциональным, синтаксис или семантику? Семантика функционального программирования, безусловно, может быть добавлена ​​в компилятор C, но к тому времени, когда вы закончите, вы по существу получите эквивалент одного из существующих функциональных языков, таких как Scheme, Haskell и т. Д.

Было бы лучше использовать время только для изучения синтаксиса тех языков, которые непосредственно поддерживают эту семантику.

Барри Браун
источник
-3

Не знаю о C. Есть некоторые функциональные возможности в Objective-C, хотя, GCC на OSX также поддерживает некоторые функции, однако я бы снова рекомендовал начать использовать функциональный язык, есть много упомянутых выше. Я лично начал со схемы, есть несколько отличных книг, таких как «Маленький интриган», которые могут помочь вам в этом.

Шив
источник