¿Qué herramientas hay para la programación funcional en C?

153

Últimamente he estado pensando mucho sobre cómo hacer programación funcional en C ( no en C ++). Obviamente, C es un lenguaje de procedimiento y realmente no admite la programación funcional de forma nativa.

¿Hay alguna extensión de compilador / lenguaje que agregue algunas construcciones de programación funcional al lenguaje? GCC proporciona funciones anidadas como una extensión de lenguaje; Las funciones anidadas pueden acceder a las variables desde el marco de la pila principal, pero esto todavía está muy lejos de los cierres maduros.

Por ejemplo, una cosa que creo que podría ser realmente útil en C es que en cualquier lugar donde se espera un puntero de función, podría pasar una expresión lambda, creando un cierre que se desintegra en un puntero de función. C ++ 0x va a incluir expresiones lambda (que creo que es increíble); Sin embargo, estoy buscando herramientas aplicables a la C.

[Editar] Para aclarar, no estoy tratando de resolver un problema particular en C que sería más adecuado para la programación funcional; Simplemente tengo curiosidad acerca de qué herramientas hay si quisiera hacerlo.

Adam Rosenfield
fuente
1
En lugar de buscar hacks y extensiones no portátiles para tratar de convertir C en algo que no es, ¿por qué no usas un lenguaje que proporciona la funcionalidad que estás buscando?
Robert Gamble,
Consulte también la pregunta relacionada: < stackoverflow.com/questions/24995/… >
Andy Brice
@AndyBrice Esa pregunta es sobre un lenguaje diferente y en realidad no parece tener sentido (C ++ no tiene un "ecosistema" en el mismo sentido que.)
Kyle Strand

Respuestas:

40

FFCALL le permite crear cierres en C: callback = alloc_callback(&function, data)devuelve un puntero de función que callback(arg1, ...)es equivalente a llamar function(data, arg1, ...). Sin embargo, tendrá que manejar la recolección de basura manualmente.

Relacionado, se han agregado bloques al tenedor de GCC de Apple; no son punteros de función, pero le permiten pasar lambdas mientras evitan la necesidad de construir y liberar almacenamiento para las variables capturadas a mano (de hecho, ocurre una copia y un recuento de referencias, oculto detrás de algunas bibliotecas sintácticas de azúcar y tiempo de ejecución).

efímero
fuente
89

Puede usar las funciones anidadas de GCC para simular expresiones lambda, de hecho, tengo una macro para hacerlo por mí:

#define lambda(return_type, function_body) \
  ({ \
    return_type anon_func_name_ function_body \
    anon_func_name_; \
  })

Usar así:

int (*max)(int, int) = lambda (int, (int x, int y) { return x > y ? x : y; });
Joe D
fuente
12
Esto es realmente genial. Parece que __fn__es solo un nombre arbitrario para la función definida dentro del bloque ({... }), ¿no es una extensión GCC o una macro predefinida? La elección del nombre para __fn__(que se parece mucho a la definición de GCC) realmente me hizo rascarme la cabeza y buscar en la documentación de GCC sin efecto.
FooF
1
Lamentablemente, esto no funciona en la práctica: si desea que el cierre capture algo, requiere una pila ejecutable, que es una práctica de seguridad terrible. Personalmente, no creo que sea útil en absoluto.
Demi el
No es remotamente útil, pero es divertido. Por eso se acepta la otra respuesta.
Joe D
65

La programación funcional no se trata de lambdas, se trata de funciones puras. Entonces, lo siguiente promueve ampliamente el estilo funcional:

  1. Solo use argumentos de función, no use estado global.

  2. Minimice los efectos secundarios, es decir, printf o cualquier IO. Devuelve datos que describen IO que se pueden ejecutar en lugar de causar los efectos secundarios directamente en todas las funciones.

Esto se puede lograr en plano C, sin necesidad de magia.

Andy hasta
fuente
55
Creo que has llevado esa visión extrema de la programación funcional al punto de lo absurdo. ¿De qué sirve una especificación pura de, por ejemplo, mapsi uno no tiene las facilidades para pasarle una función?
Jonathan Leonard
27
Creo que este enfoque es mucho más pragmático que usar macros para crear un lenguaje funcional dentro de c. El uso apropiado de funciones puras es más probable que mejore un sistema que el uso de map en lugar de for loops.
Andy hasta
66
Seguramente sabes que el mapa es solo el punto de partida. Sin funciones de primera clase, cualquier programa (incluso si todas sus funciones son 'puras') será de orden inferior (en el sentido de la teoría de la información) que su equivalente con funciones de primera clase. En mi opinión, es este estilo declarativo solo posible con funciones de primera clase el principal beneficio de FP. Creo que le está haciendo un mal servicio a alguien para implicar que la verbosidad que resulta de la falta de funciones de primera clase es todo lo que FP tiene para ofrecer. Cabe señalar que, históricamente, el término FP implicaba funciones de primera clase más que pureza.
Jonathan Leonard
PD: El comentario anterior era del móvil; la gramática irregular no fue intencional.
Jonathan Leonard
1
La respuesta de Andy Tills muestra una visión muy pragmática de las cosas. Ir "puro" en C no requiere nada sofisticado y produce un gran beneficio. Funciones de primera clase, en comparación con "comodidad de vida". Es conveniente, pero adoptar un estilo puro de programación es práctico. Me pregunto por qué nadie discute las llamadas de cola en relación con todo esto. Aquellos que desean funciones de primera clase también deben solicitar llamadas de cola.
BitTickler
17

El libro de Hartel y Muller, Functional C , se puede encontrar hoy en día (2012-01-02) en: http://eprints.eemcs.utwente.nl/1077/ (hay un enlace a la versión PDF).

FooF
fuente
2
No hay ningún intento de nada funcional en este libro (en relación con la pregunta).
Eugene Tolmachev
44
Parece que tienes toda la razón. De hecho, el prefacio establece que el propósito del libro es enseñar programación imperativa después de que el estudiante ya se haya familiarizado con la programación funcional. Para su información, esta fue mi primera respuesta en SO y se escribió como una respuesta solo porque no había requerido reputación para comentar una respuesta anterior con un enlace podrido. Siempre me pregunto por qué la gente mantiene +1 esta respuesta ... ¡Por favor no hagas eso! :-)
FooF
1
Quizás el libro debería haberse llamado mejor Programación posfuncional (en primer lugar porque "Imperativo C" no suena sexy, en segundo lugar porque asume la familiaridad con el paradigma de programación funcional, en tercer lugar como un juego de palabras porque el paradigma imperativo parece una disminución de los estándares y funcionalidad correcta, y quizás cuarto para referirse a la noción de programación posmoderna de Larry Wall, aunque este libro fue escrito antes del artículo / presentación de Larry Wall).
FooF
Y esta es una respuesta duplicada
PhilT
8

El requisito previo para el estilo de programación funcional es una función de primera clase. Podría simularse en C portátil si tolera lo siguiente:

  • gestión manual de enlaces de ámbito léxico, también conocidos como cierres.
  • Gestión manual de las variables de función de por vida.
  • sintaxis alternativa de aplicación de función / llamada.
/* 
 * 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;

el tiempo de ejecución de dicho código podría ser tan pequeño como el siguiente

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

En esencia, imitamos la función de primera clase con cierres representados como un par de funciones / argumentos más macros. El código completo se puede encontrar aquí .

Viktor Shepel
fuente
7

Lo principal que viene a la mente es el uso de generadores de código. ¿Estaría dispuesto a programar en un lenguaje diferente que proporcionara la programación funcional y luego generar el código C a partir de eso?

Si esa no es una opción atractiva, entonces podría abusar de CPP para obtener parte del camino. El sistema macro debería permitirle emular algunas ideas de programación funcional. He oído decir que gcc se implementa de esta manera, pero nunca lo he comprobado.

C, por supuesto, puede pasar funciones utilizando punteros de función, los principales problemas son la falta de cierres y el sistema de tipos tiende a interferir. Podría explorar sistemas macro más potentes que CPP como M4. Supongo que, en última instancia, lo que estoy sugiriendo es que el verdadero C no está a la altura de la tarea sin un gran esfuerzo, pero podría extender C para que esté a la altura de la tarea. Esa extensión se parecería más a C si usa CPP o podría ir al otro extremo del espectro y generar código C desde otro lenguaje.

Jason Dagit
fuente
Esta es la respuesta más honesta. Intentar escribir C de manera funcional es luchar contra el diseño y la estructura del lenguaje en sí.
josiah
5

Si desea implementar cierres, tendrá que ponerse firme con el lenguaje ensamblador y el intercambio / gestión de la pila. No lo recomiendo, solo digo que eso es lo que tendrás que hacer.

Sin embargo, no estoy seguro de cómo manejará las funciones anónimas en C. En una máquina von Neumann, podría hacer funciones anónimas en asm.

Paul Nathan
fuente
2

El lenguaje Felix compila a C ++. Tal vez eso podría ser una piedra angular, si no te importa C ++.

LennyStackOverflow
fuente
9
⁻¹ porque α) El autor mencionó explícitamente «no C ++», β) El C ++ producido por un compilador no sería un «C ++ legible por humanos». γ) El estándar C ++ admite una programación funcional, no es necesario utilizar un compilador de un lenguaje a otro.
Hola Ángel,
Una vez que se te ocurre un lenguaje funcional por medio de macros o similares, automáticamente implicas que no te importa demasiado C. Esta respuesta es válida y está bien porque incluso C ++ comenzó como una fiesta macro sobre C. Si Si vas por ese camino lo suficiente, terminarás con un nuevo idioma. Entonces solo se trata de una discusión de fondo.
BitTickler
1

Un buen número de lenguajes de programación están escritos en C. Y algunos de ellos admiten funciones como ciudadanos de primera clase, los idiomas en esa área son ecl (embebible common lisp IIRC), Gnu Smalltalk (gst) (Smalltalk tiene bloques), luego hay bibliotecas para "cierres", por ejemplo, en glib2 http://library.gnome.org/devel/gobject/unstable/chapter-signal.html#closure que al menos se acercaba a la programación funcional. Entonces, tal vez usar algunas de esas implementaciones para hacer programación funcional puede ser una opción.

Bueno, o puedes ir a aprender Ocaml, Haskell, Mozart / Oz o similares ;-)

Saludos

Friedrich
fuente
¿Te importaría decirme qué tiene de malo el uso de bibliotecas que al menos admiten el estilo de programación FP?
Friedrich
1

La forma en que hice la programación funcional en C fue escribir un intérprete de lenguaje funcional en C. Lo llamé Fexl, que es la abreviatura de "Function EXpression Language".

El intérprete es muy pequeño, compilando hasta 68K en mi sistema con -O3 habilitado. Tampoco es un juguete: lo estoy usando para todo el nuevo código de producción que escribo para mi negocio (contabilidad basada en web para asociaciones de inversión).

Ahora escribo código C solo para (1) agregar una función incorporada que llama a una rutina del sistema (por ejemplo, fork, exec, setrlimit, etc.), u (2) optimizar una función que de otro modo podría escribirse en Fexl (por ejemplo, buscar para una subcadena).

El mecanismo del módulo se basa en el concepto de "contexto". Un contexto es una función (escrita en Fexl) que asigna un símbolo a su definición. Cuando lee un archivo Fexl, puede resolverlo con el contexto que desee. Esto le permite crear entornos personalizados o ejecutar código en un "sandbox" restringido.

http://fexl.com

Patrick Chkoreff
fuente
-3

¿Qué tiene C que desea que sea funcional, la sintaxis o la semántica? La semántica de la programación funcional ciertamente podría agregarse al compilador de C, pero para cuando haya terminado, esencialmente tendría el equivalente de uno de los lenguajes funcionales existentes, como Scheme, Haskell, etc.

Sería un mejor uso del tiempo aprender la sintaxis de esos idiomas que admiten directamente esa semántica.

Barry Brown
fuente
-3

No sé acerca de C. Sin embargo, hay algunas características funcionales en Objective-C, GCC en OSX también admite algunas características, sin embargo, recomendaría nuevamente comenzar a usar un lenguaje funcional, hay muchas mencionadas anteriormente. Personalmente comencé con Scheme, hay algunos libros excelentes como The Little Schemer que pueden ayudarte a hacerlo.

Shiv
fuente