Подтвердить что ты не робот

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

4b9b3361

Ответ 1

Если вы заинтересованы в том, чтобы делать это в простой 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...
}

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

Ответ 2

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

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

Ответ 3

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

Ответ 4

В прямом 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, и я считаю, что они находятся в следующем стандарте С++ 0x, поэтому они не так уж необычны.

Ответ 5

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

Ответ 6

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

Ответ 7

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

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

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

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

Ответ 8

Это ответ на вопрос: как создавать функции в 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) как короткую нотацию для cons (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. И убедитесь, что все смежные функции имеют совместимые типы.

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