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

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

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

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

Итак, это в основном мой план:

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. Я бы очень хотел сохранить стиль функции более высокого порядка, но если это не одобряется, я очень ценю способы сделать это по-другому.

8 ответов


если вы хотите сделать это в простом C, вам нужно не забыть включить опцию передачи в контекстный указатель от вызывающего функтора (функция более высокого порядка) к переданной функции. Это позволяет имитировать достаточно закрытия, что вы можете сделать вещи работать достаточно легко. На что указывает этот указатель... ну, это зависит от вас, но это должно быть void* в API функтора (или один из многих псевдонимов для него, например gpointer в бойком мире или ClientData в 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, уже более высокого порядка.

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


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


в прямом c это действительно делается только через указатели функций, которые являются болью и не предназначены для этого типа вещей (что частично объясняет, почему они являются болью). Однако блоки (или закрытия, согласно не-apple) являются фантастическими для этого. Они компилируются в 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) с пробелом между ним, затем добавляет все символы вместе в accum, и каждый раз, когда он это делает, он снова печатает список символов.

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


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


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

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

#include<stdlib.h>
#include<malloc.h>

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

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

comp((f g h),x)

=

f(comp((g h),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 в bind и функции библиотеки). Наконец,C++0x добавляет встроенную поддержку лямбда-функций, который заботится о вас записи в закрытии всех переменных, что ваша функция зависит от.


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

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

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

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