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

Функциональное программирование (Currying) в C/Проблема с типами

Как функциональный программист с окрашенной шерстью, мне трудно не пытаться обучать мою любимую парадигму на любом языке, который я использую. При написании некоторых C я нашел, что хотел бы выполнить одну из моих функций, а затем передать частично примененную функцию. После чтения Есть ли способ сделать currying в C? и прислушаться к предупреждениям на http://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html#Nested-Functions Я придумал:

#include <stdio.h>

typedef int (*function) (int);

function g (int a) {
    int f (int b) {
        return a+b;
    }
    return f;
}

int f1(function f){
    return f(1);}

int main () {
    printf ("(g(2))(1)=%d\n",f1(g(2)));
}

Работа выполняется так, как ожидалось. Однако моя оригинальная программа работает с double, поэтому я подумал, что просто изменил соответствующие типы, и все будет в порядке:

#include <stdio.h>

typedef double (*function) (double);

function g (double a) {
    double f (double b) {
        return a+b;
    }
    return f;
}

double f1(function f){
    return f(1);}

int main () {
    printf ("(g(2))(1)=%e\n",f1(g(2)));
}

Это создает такие вещи, как:

bash-3.2$ ./a.out 
Segmentation fault: 11
bash-3.2$ ./a.out 
Illegal instruction: 4

с выбором ошибки, кажущейся случайной. Кроме того, если какой-либо пример скомпилирован с помощью -O3, сам компилятор выдает сам Segmentation fault: 11. Я не получаю никаких предупреждений от gcc в любой момент, и я не могу понять, что происходит. Кто-нибудь знает, почему вторая программа терпит неудачу, а первая нет? Или еще лучше, как исправить второй?

Мой gcc i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00), а ядро ​​Darwin Kernel Version 12.1.0: Tue Aug 14 13:29:55 PDT 2012; root:xnu-2050.9.2~1/RELEASE_X86_64.

edit: Чтобы быть ясным, я понимаю, что то, что я пытаюсь сделать, глупо. Этот код не будет запускаться на любовнике или NYSE. Я пытаюсь понять больше о том, как работают указатели функций в (GNU) C, и объяснить что-то интересное, что я нашел. Я обещаю никогда не делать ничего подобного в реальном мире.

4b9b3361

Ответ 1

Интересный вопрос, и я взглянул на статью в приведенном ответе (Более функциональное повторное использование в C/С++/ Objective-C с функциями Curried).

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

Казалось, что есть две проблемы.

Прежде всего, это возможность обрабатывать произвольные вызовы функций с произвольными списками аргументов. Подход, который я принял, состоял в использовании стандартных аргументов аргументов переменной C Library (va_list с функциями va_start(), va_arg() и va_end()), а затем сохранить указатель функции вместе с предоставленными аргументами в область данных, чтобы они затем может быть выполнена позднее. Я заимствовал и изменил, как функция printf() использует строку формата, чтобы узнать, сколько аргументов и их типов предоставлены.

Следующее - это хранение функции и списка ее аргументов. Я просто использовал структуру с произвольным размером, чтобы попробовать эту концепцию. Это потребует немного больше мысли.

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

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

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

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

Вам нужно будет добавить другие случаи в коммутатор, чтобы иметь возможность обрабатывать другие типы аргументов. Я просто сделал несколько для доказательства концепции.

Также это не делает функцию, вызывающую функцию, хотя это казалось бы на поверхности довольно простым расширением. Как я уже сказал, я не полностью понимаю эту ситуацию.

#include <stdarg.h>
#include <string.h>

// a struct which describes the function and its argument list.
typedef struct {
    void (*f1)(...);
    // we have to have a struct here because when we call the function,
    // we will just pass the struct so that the argument list gets pushed
    // on the stack.
    struct {
        unsigned char myArgListArray[48];   // area for the argument list.  this is just an arbitray size.
    } myArgList;
} AnArgListEntry;

// these are used for simulating a stack.  when functions are processed
// we will just push them onto the stack and later on we will pop them
// off so as to run them.
static unsigned int  myFunctionStackIndex = 0;
static AnArgListEntry myFunctionStack[1000];

// this function pushes a function and its arguments onto the stack.
void pushFunction (void (*f1)(...), char *pcDescrip, ...)
{
    char *pStart = pcDescrip;
    AnArgListEntry MyArgList;
    unsigned char *pmyArgList;
    va_list argp;
    int     i;
    char    c;
    char   *s;
    void   *p;

    va_start(argp, pcDescrip);

    pmyArgList = (unsigned char *)&MyArgList.myArgList;
    MyArgList.f1 = f1;
    for ( ; *pStart; pStart++) {
        switch (*pStart) {
            case 'i':
                // integer argument
                i = va_arg(argp, int);
                memcpy (pmyArgList, &i, sizeof(int));
                pmyArgList += sizeof(int);
                break;
            case 'c':
                // character argument
                c = va_arg(argp, char);
                memcpy (pmyArgList, &c, sizeof(char));
                pmyArgList += sizeof(char);
                break;
            case 's':
                // string argument
                s = va_arg(argp, char *);
                memcpy (pmyArgList, &s, sizeof(char *));
                pmyArgList += sizeof(char *);
                break;
            case 'p':
                // void pointer (any arbitray pointer) argument
                p = va_arg(argp, void *);
                memcpy (pmyArgList, &p, sizeof(void *));
                pmyArgList += sizeof(void *);
                break;
            default:
                break;
        }
    }
    va_end(argp);
    myFunctionStack[myFunctionStackIndex] = MyArgList;
    myFunctionStackIndex++;
}

// this function will pop the function and its argument list off the top
// of the stack and execute it.
void doFuncAndPop () {
    if (myFunctionStackIndex > 0) {
        myFunctionStackIndex--;
        myFunctionStack[myFunctionStackIndex].f1 (myFunctionStack[myFunctionStackIndex].myArgList);
    }
}

// the following are just a couple of arbitray test functions.
// these can be used to test that the functionality works.
void myFunc (int i, char * p)
{
    printf (" i = %d, char = %s\n", i, p);
}

void otherFunc (int i, char * p, char *p2)
{
    printf (" i = %d, char = %s, char =%s\n", i, p, p2);
}

void mySignal (int sig, void (*f)(void))
{
    f();
}

int main(int argc, char * argv[])
{
    int i = 3;
    char *p = "string";
    char *p2 = "string 2";

    // push two different functions on to our stack to save them
    // for execution later.
    pushFunction ((void (*)(...))myFunc, "is", i, p);
    pushFunction ((void (*)(...))otherFunc, "iss", i, p, p2);

    // pop the function that is on the top of the stack and execute it.
    doFuncAndPop();

    // call a function that wants a function so that it will execute
    // the current function with its argument lists that is on top of the stack.
    mySignal (1, doFuncAndPop);

    return 0;
}

Дополнительный бит удовольствия, который вы можете иметь с этим, заключается в использовании функции pushFunction() в функции, которая вызывается doFuncAndPop(), чтобы иметь другую функцию, которую вы можете поместить в стек с ее аргументами.

Например, если вы измените функцию otherFunc() в источнике выше, чтобы выглядеть следующим образом:

void otherFunc (int i, char * p, char *p2)
{
    printf (" i = %d, char = %s, char =%s\n", i, p, p2);
    pushFunction ((void (*)(...))myFunc, "is", i+2, p);
}

если вы затем добавите еще один вызов doFuncAndPop(), вы увидите, что выполняется первый otherFunc(), тогда выполняется вызов myFunc(), который был запущен в otherFunc(), а затем, наконец, вызов myFunc(), который был нажат в main ().

ИЗМЕНИТЬ 2: Если мы добавим следующую функцию, это выполнит все функции, которые были помещены в стек. Это позволит нам в основном создать небольшую программу, нажав функции и аргументы на наш стек, а затем выполнив серию вызовов функций. Эта функция также позволит нам нажимать функцию без каких-либо аргументов, а затем нажимать некоторые аргументы. Когда функция popping отключается от нашего стека, если блок аргументов не имеет допустимого указателя функции, тогда мы должны поместить этот список аргументов в блок аргументов в верхней части стека и затем выполнить его. Аналогичное изменение может быть сделано и для функции doFuncAndPop(). И если мы используем операцию pushFunction() в выполняемой функции, мы можем сделать некоторые интересные вещи.

На самом деле это может быть основой для Threaded Interpreter.

// execute all of the functions that have been pushed onto the stack.
void executeFuncStack () {
    if (myFunctionStackIndex > 0) {
        myFunctionStackIndex--;
        // if this item on the stack has a function pointer then execute it
        if (myFunctionStack[myFunctionStackIndex].f1) {
            myFunctionStack[myFunctionStackIndex].f1 (myFunctionStack[myFunctionStackIndex].myArgList);
        } else if (myFunctionStackIndex > 0) {
            // if there is not a function pointer then assume that this is an argument list
            // for a function that has been pushed on the stack so lets execute the previous
            // pushed function with this argument list.
            int myPrevIndex = myFunctionStackIndex - 1;
            myFunctionStack[myPrevIndex].myArgList = myFunctionStack[myFunctionStackIndex].myArgList;
        }
        executeFuncStack();
    }
}

ИЗМЕНИТЬ 3: Затем мы вносим изменения в pushFunc() для обработки двойного со следующим дополнительным переключателем:

case 'd':
  {
     double d;
     // double argument
     d = va_arg(argp, double);
     memcpy (pmyArgList, &d, sizeof(double));
     pmyArgList += sizeof(double);
   }
break;

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

double f1 (double myDouble)
{
    printf ("f1 myDouble = %f\n", myDouble);
    return 0.0;
}

double g2 (double myDouble) {
    printf ("g2 myDouble = %f\n", myDouble);
    myDouble += 10.0;
    pushFunction (0, "d", myDouble);
    return myDouble;
}

В новой версии мы используем наши новые функции со следующими операциями:

double xDouble = 4.5;
pushFunction ((void (*)(...))f1, 0);
pushFunction ((void (*)(...))g2, "d", xDouble);
executeFuncStack();

Эти операторы будут выполнять сначала функцию g2() со значением 4.5, а затем функция g2() будет выталкивать свое возвращаемое значение в наш стек, который будет использоваться функцией f1(), которая была сначала нажата на наш стек.

Ответ 2

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

Поэтому не полагайтесь на поведение undefined. Не претендуйте на то, чтобы "прислушаться к предупреждениям", когда на самом деле вы действовали против этих предупреждений. В предупреждении четко указано:

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

В C нет закрытия, поэтому в этом смысле не может быть currying. Вы можете получить аналогичные эффекты, если вы передадите некоторые данные для вызова функций, но это не будет выглядеть точно так же, как обычный вызов функции, поэтому он не будет похож на обычное каррирование. С++ обладает большей гибкостью, поскольку он позволяет объектам вести себя как синтаксически подобные функции. В мире С++ каррирование обычно называется привязка" параметров функции.

Если вы действительно хотите знать, почему одна часть кода работала, а другая не удалась, вы можете взять код сборки (сгенерированный, например, gcc -S -fverbose-asm), и имитировать выполнение в вашей голове, чтобы узнать, что происходит с вашими данными и вещи. Или вы можете использовать отладчик, чтобы увидеть, где что-то не удается, или изменить местоположение данных. Могу взять какую-то работу, и я сомневаюсь, что это того стоит.

Ответ 3

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

#include <stdio.h>

// Basic function summing its arguments
double g (double a, double b)
{
    return a+b;
}

double f1(double a)
{
    /* "1" can be replaced by a static initialized
       by another function, e.g.
       static double local_b = g(0, 1);
    */
    return g(a, 1);
}

int main () {
    printf ("(g(2))(1)=%f\n", f1(2));
}

Ответ 4

Фиксированная версия выше кода

#include <stdio.h>

typedef double (*function) (double,double);

// Basic function summing its arguments
double g (double a, double b)
{
        return a+b;
}

double f1(function wrapfunc,double a)
{
 /* "1" can be replaced by a static initialized
     by another function, e.g.
     static double local_b = g(0, 1);
 */
 return wrapfunc(a, 1);  
}

int main () {
        printf ("(g(2))(1)=%f\n", f1(g,2));
}

немного больше о различных функциях работы с примером примера параметров

#include<iostream>
#include<cstdio>

using namespace std;

#define N 4

#define LOOP(i) for(i=0; i<N; i++)

#define F(i) ( (int(*)(int,int))opt[i] )
#define FI F(i)
#define FJ F(j)
#define FK F(k)


int add(int a, int b) { return a + b; }

int sub(int a, int b) { return a - b; }

u int mul (int a, int b) {return a * b; }

int div(int a, int b) {
    if (b == 0 || a % b)
        return 2401;
    return a / b;
}

char whichOpt(int index)
{
    if (index == 0) return '+';
    else if (index == 1) return '-';
    else if (index == 2) return '*';
    return '/';
}

void howObtain24(int num[], void *opt[])
{
    int i, j, k, a, b, c, d;
    int ans=0;
    LOOP(i) LOOP(j) LOOP(k)
         LOOP(a) LOOP(b) LOOP(c) LOOP(d)
    {
        if (a == b || a == c || a == d || b == c || b == d || c == d)
            continue;
        if (FI(FJ(FK(num[a], num[b]), num[c]), num[d]) == 24) {
            std::cout << "((" << num[a] << whichOpt(k) << num[b] << ')'
                 << whichOpt(j) << num[c] << ')' << whichOpt(i) << num[d] << endl;
            ans++;
            continue;
        }
        if (FI(FJ(num[a], num[b]), FK(num[c], num[d])) == 24) {
            std::cout << '(' << num[a] << whichOpt(j) << num[b] << ')'
                 << whichOpt(i) << '(' << num[c] << whichOpt(k) << num[d] << ')' << endl;
            ans++;
            continue;
        }
    }
    if(ans==0)
    std::cout << "Non-Answer" << std::endl;
    return;

}

//=======================================================================
int main() {

    int num[N];

    void *opt[N] = { (void *)add, (void *)sub, (void *)mul, (void *)div };

    std::cout << "Input 4 Numbers between 1 and 10\n"
    for (int i = 0; i < N; i++)
        cin >> num[i];

    for (int j = 0; j < N; j++)
        if (num[j] < 1 || num[j] > 10) {
            std::cout << "InCorrect Input\n"

            return 0;
        }
        howObtain24(num, opt);

        return 0;
}