Функции более высокого порядка в C

Существует ли «правильный» способ реализации функций более высокого порядка в C.

Мне в основном интересуются такие вещи, как переносимость и правильность синтаксиса здесь, и если есть несколько способов, каковы достоинства и недостатки.

Изменить. Причина, по которой я хочу знать, как создавать функции более высокого порядка, заключается в том, что я написал систему для преобразования списков PyObject (которые вы получаете при вызове сценариев python) в список структур C, содержащих одни и те же данные, но организованные таким образом, зависит от библиотек python.h. Поэтому мой план состоит в том, чтобы иметь функцию, которая выполняет итерацию через список pythonic и вызывает функцию для каждого элемента в списке и помещает результат в список, который он возвращает.

Так что это в основном мой план:

typedef gpointer (converter_func_type)(PyObject *) gpointer converter_function(PyObject *obj) { // do som stuff and return a struct cast into a gpointer (which is a void *) } GList *pylist_to_clist(PyObject *obj, converter_func_type f) { GList *some_glist; for each item in obj { some_glist = g_list_append(some_glist, f(item)); } return some_glist; } void some_function_that_executes_a_python_script(void) { PyObject *result = python stuff that returns a list; GList *clist = pylist_to_clist(result, converter_function); } 

И чтобы уточнить вопрос: я хочу знать, как это сделать в более безопасном и более правильном C. Я бы очень хотел сохранить стиль функции более высокого порядка, но если это неодобрительно, я очень ценю, как это сделать другим способом.

Если вы хотите сделать это на простом C, вам нужно не забудьте включить опцию передать указатель контекста от вызывающего функтора (функции более высокого порядка) к переданной функции. Это позволяет достаточно симулировать закрытия, что вы можете сделать вещи работать достаточно легко. На что указывает этот указатель … ну, это зависит от вас, но это должно быть void* в API-интерфейсе functor (или одном из многих псевдонимов для него, например gpointer в мире GLib или ClientData в API Tcl C ).

[EDIT]: использовать / адаптировать свой пример:

 typedef gpointer (converter_func_type)(gpointer,PyObject *) gpointer converter_function(gpointer context_ptr,PyObject *obj) { int *number_of_calls_ptr = context_ptr; *number_of_calls_ptr++; // do som stuff and return a struct cast into a gpointer (which is a void *) } GList *pylist_to_clist(PyObject *obj, converter_func_type f, gpointer context_ptr) { GList *some_glist; for each item in obj { some_glist = g_list_append(some_glist, f(context_ptr,item)); } return some_glist; } void some_function_that_executes_a_python_script(void) { int number_of_calls = 0; PyObject *result = python stuff that returns a list; GList *clist = pylist_to_clist(result, converter_function, &number_of_calls); // Now number_of_calls has how often converter_function was called... } 

Это тривиальный пример того, как это сделать, но он должен показать вам путь.

Технически функции более высокого порядка – это просто функции, которые принимают или возвращают функции. Итак, такие вещи, как qsort, уже более высокого порядка.

Если вы имеете в виду нечто большее, чем функции lambda, найденные в функциональных языках (именно поэтому функции более высокого порядка действительно становятся полезными), это довольно сложно и не может быть сделано естественным образом в текущем стандарте C. Они просто не являются частью язык. Расширение блоков Apple является лучшим кандидатом. Он работает только в GCC (и компиляторе LLVM’s C), но они действительно полезны. Надеюсь, что-то подобное поймает. Вот несколько релевантных ресурсов:

  • Документация Apple по этой функции (ссылается на некоторые технологии Apple, а также на Objective-C, но основной материал блока является частью их расширения на C)
  • Вот хорошее введение в блоки
  • Обзор cocoa для ученых по блокам C

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

В прямом c это действительно делается только с помощью указателей функций, которые являются одновременно и болью, и не предназначены для такого типа вещей (что частично объясняется тем, что это боль). Блоки (или закрытие, в соответствии с не-яблоком) являются фантастическими для этого. Они компилируются в gcc-4.x или что-то еще, и icc-то, но независимо от того, что вы ищете. К сожалению, я не могу найти хорошие учебники в Интернете, но достаточно сказать, что это работает примерно так:

 void iterate(char *str, int count, (^block)(str *)){ for(int i = 0; i < count; i++){ block(list[i]); } } main() { char str[20]; iterate(str, 20, ^(char c){ printf("%c ", c); }); int accum = 0; iterate(someList, 20, ^(char c){ accum += c; iterate(str, 20, ^(char c){ printf("%c ", c); }); }); } 

очевидно, этот код бессмыслен, но он печатает каждый символ строки (str) с пробелом между ними, а затем добавляет все символы вместе в накопление, и каждый раз, когда он это делает, он снова выводит список символов.

Надеюсь это поможет. Кстати, блоки очень заметны в Mac OS X Snow Leopard api-s, и я считаю, что они находятся в предстоящем стандарте C ++ 0x, поэтому они не так уж необычны.

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

Это ответ на вопрос: как создавать функции в C, которые здесь перенаправляются.

Вы можете создать структуру данных для реализации типа данных списка. эта структура может содержать указатели на функции.

 #include #include typedef (*fun)(); typedef struct funList { fun car; struct funList *cdr;} *funList; const funList nil = NULL; int null(funList fs){ return nil==fs; } fun car(funList fs) { if(!null(fs)) return fs->car; else { fprintf(stderr,"error:can't car(nil) line:%d\n",__LINE__); exit(1); } } funList cdr(funList ls) { if(!null(ls)) return ls->cdr; else { fprintf(stderr,"error:can't cdr(nil) line:%d\n",__LINE__); exit(1); } } funList cons(fun f, funList fs) { funList ls; ls=(funList) malloc(sizeof(struct funList)); if(NULL==ls) { fprintf(stderr,"error:can't alloc mem for cons(...) line:%d\n",__LINE__); exit(1); } ls->car=f; ls->cdr=fs; return ls; } 

мы можем написать функцию comp, которая применяет список функций:

 type_2 comp(funList fs, type_1 x) { return (null(fs)) ? x : car(fs)(comp(cdr(fs),x)); } 

Пример того, как это работает. Мы используем (fgh) как короткую нотацию для cons (f, cons (g, cons (h, nil))), которая применяется к заданному аргументу x:

 comp((fgh),x) 

знак равно

 f(comp((gh),x)) 

знак равно

 f(g(comp((h),x))) 

знак равно

 f(g(h(comp(nil,x)))) 

знак равно

 f(g(h(x))) 

если вы использовали тип полиморфного списка на типизированном языке, таком как SML или Haskell, тип comp должен быть:

 comp :: ([a -> a],a) -> a 

потому что в этом контексте все члены в списке имеют один и тот же тип. C может быть более гибким в этом смысле. Возможно, что-то вроде

 typedef void (*fun)(); 

или же

 typedef (*fun)(); 

вы должны увидеть, что об этом говорит инструкция C. И убедитесь, что все смежные функции имеют совместимые типы.

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

Это очень сложно сделать в прямом C. Это более возможно в C ++ (см. Руководство по функциям или библиотеки привязки и функций Boost). Наконец, C ++ 0x добавляет встроенную поддержку lambda-функций , которая заботится о том, чтобы вы захватили при закрытии все переменные, от которых зависит ваша функция.

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

[Edit] Я предположил, что единственный способ достичь этого – использовать язык сценариев. Другие меня вызвали. Итак, я заменяю это предложение следующим: [/ Edit]

Чего вы пытаетесь достичь? Если вы хотите подражать закрытию, используйте язык, который их поддерживает (вы можете связываться с Ruby, lua, javascript и т. Д. Через библиотеки). Если вы хотите использовать обратные вызовы, указатели на функции в порядке. Указатели функций объединяют наиболее опасные области C (указатели и систему слабых типов), поэтому будьте осторожны. Объявление указателей функций тоже не интересно читать.

Вы находите некоторые библиотеки C, используя указатели функций, потому что они должны. Если вы пишете библиотеку, возможно, вам тоже нужно их использовать. Если вы просто используете их в своем коде, вы, вероятно, не думаете о C. Вы думаете в lisp или схеме или rubyе или … и пытаетесь написать его в C. Изучите C-путь.