Quais ferramentas existem para programação funcional em C?

153

Ultimamente, tenho pensado muito em como fazer programação funcional em C ( não em C ++). Obviamente, C é uma linguagem processual e realmente não suporta programação funcional nativamente.

Existem extensões de compilador / idioma que adicionam algumas construções de programação funcional ao idioma? O GCC fornece funções aninhadas como uma extensão de idioma; funções aninhadas podem acessar variáveis ​​do quadro de pilha pai, mas isso ainda está muito longe de fechamentos maduros.

Por exemplo, uma coisa que eu acho que poderia ser realmente útil em C é que, em qualquer lugar em que um ponteiro de função seja esperado, você poderá passar uma expressão lambda, criando um fechamento que se deteriora em um ponteiro de função. C ++ 0x vai incluir expressões lambda (o que eu acho incrível); no entanto, estou procurando ferramentas aplicáveis ​​ao C. direto

[Editar] Para esclarecer, não estou tentando resolver um problema específico em C que seria mais adequado à programação funcional; Estou apenas curioso sobre quais ferramentas existem, se eu quisesse fazê-lo.

Adam Rosenfield
fonte
1
Em vez de procurar hacks e extensões não portáteis para tentar transformar C em algo que não é, por que você não usa apenas uma linguagem que forneça a funcionalidade que você está procurando?
Robert Gamble
Consulte também a pergunta relacionada: < stackoverflow.com/questions/24995/… >
Andy Brice
@AndyBrice Essa pergunta é sobre uma língua diferente e na verdade não parece fazer sentido (C ++ não tem um "ecossistema" no mesmo sentido como.
Kyle Strand

Respostas:

40

FFCALL permite criar fechamentos em C - callback = alloc_callback(&function, data)retorna um ponteiro de função callback(arg1, ...)equivalente à chamada function(data, arg1, ...). Você terá que lidar com a coleta de lixo manualmente, no entanto.

De maneira semelhante, foram adicionados blocos ao fork do GCC da Apple; eles não são indicadores de função, mas permitem distribuir lambdas, evitando a necessidade de criar e liberar armazenamento manualmente para variáveis ​​capturadas (efetivamente, algumas cópias e contagem de referência acontecem, ocultas atrás de algumas bibliotecas sintáticas de açúcar e tempo de execução).

efémero
fonte
89

Você pode usar as funções aninhadas do GCC para simular expressões lambda; na verdade, tenho uma macro para fazer isso por mim:

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

Use assim:

int (*max)(int, int) = lambda (int, (int x, int y) { return x > y ? x : y; });
Joe D
fonte
12
Isso é muito legal. Parece que __fn__é apenas um nome arbitrário para a função definida dentro do bloco ({... }), não alguma extensão GCC ou macro predefinida? A escolha do nome __fn__(parecido com a definição do GCC) realmente me fez coçar a cabeça e pesquisar na documentação do GCC sem nenhum bom efeito.
Foof
1
Infelizmente, isso não funciona na prática: se você deseja que o fechamento capture qualquer coisa, ele requer uma pilha executável, o que é uma péssima prática de segurança. Pessoalmente, não acho que seja útil.
Demi
Não é remotamente útil, mas é divertido. É por isso que a outra resposta é aceita.
Joe D
65

A programação funcional não é sobre lambdas, é sobre funções puras. Portanto, o seguinte promove amplamente o estilo funcional:

  1. Use apenas argumentos de função, não use estado global.

  2. Minimize os efeitos colaterais, por exemplo, printf ou qualquer IO. Retorne dados que descrevem E / S que podem ser executados em vez de causar efeitos colaterais diretamente em todas as funções.

Isso pode ser alcançado em c simples, sem necessidade de mágica.

Andy Till
fonte
5
Eu acho que você adotou essa visão extrema da programação funcional ao ponto do absurdo. De que serve uma especificação pura, por exemplo, mapse alguém não tem recursos para passar uma função para ela?
Jonathan Leonard
27
Eu acho que essa abordagem é muito mais pragmática do que usar macros para criar uma linguagem funcional dentro de c. Usar funções puras adequadamente é mais provável de melhorar um sistema do que usar map em vez de para loops.
Andy Till
6
Certamente você sabe que o mapa é apenas o ponto de partida. Sem funções de primeira classe, qualquer programa (mesmo que todas as suas funções sejam 'puras') será de ordem inferior (no sentido da teoria da informação) ao equivalente a funções de primeira classe. Na minha opinião, esse estilo declarativo só é possível com funções de primeira classe que é o principal benefício do FP. Eu acho que você está fazendo um desserviço a alguém para sugerir que a verbosidade resultante da falta de funções de primeira classe é tudo que a FP tem a oferecer. Deve-se notar que, historicamente, o termo FP implicava em primeira classe mais do que pureza.
Jonathan Leonard
PS O comentário anterior era do celular - a gramática irregular não era intencional.
Jonathan Leonard
1
A resposta de Andy Tills mostra uma visão muito pragmática das coisas. Tornar-se "Puro" em C não requer nada sofisticado e produz um grande benefício. Funções de primeira classe, em comparação, representam "conforto para viver". É conveniente, mas adotar um estilo puro de programação é prático. Eu me pergunto por que ninguém discute chamadas de cauda em relação a tudo isso. Aqueles que desejam funções de primeira classe também devem solicitar chamadas de chamada.
BitTickler 13/11/19
17

Atualmente, o livro de Hartel & Muller, Functional C , pode ser encontrado em (2012-01-02) em: http://eprints.eemcs.utwente.nl/1077/ (existe um link para a versão em PDF).

FooF
fonte
2
Não há nenhuma tentativa de algo funcional neste livro (como relacionado à pergunta).
Eugene Tolmachev
4
Parece que você está certo. De fato, o prefácio declara que o objetivo do livro é ensinar programação imperativa depois que o aluno já se familiarizou com a programação funcional. Para sua informação, esta foi minha primeira resposta no SO e foi escrita como resposta apenas porque eu não tinha reputação para comentar uma resposta anterior com link podre. Eu sempre me pergunto por que as pessoas mantêm +1 nesta resposta ... Por favor, não faça isso! :-)
FooF
1
Talvez o livro devesse ter sido melhor chamado de Programação Pós-Funcional (primeiro porque "Imperative C" não soa sexy, segundo porque assume familiaridade com o paradigma de programação funcional, terceiro como um trocadilho porque o paradigma imperativo parece um declínio de padrões e funcionalidade correta e talvez o quarto lugar para se referir à noção de programação pós-moderna de Larry Wall - embora este livro tenha sido escrito antes do artigo / apresentação de Larry Wall).
FooF
E esta é uma resposta duplicada
PhilT
8

O pré-requisito para o estilo de programação funcional é uma função de primeira classe. Pode ser simulado no C portátil se você tolerar a seguir:

  • gerenciamento manual de ligações de escopo lexical, também conhecidas como fechamentos.
  • gerenciamento manual de vida útil das variáveis ​​de função.
  • sintaxe alternativa da função aplicativo / chamada.
/* 
 * 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;

o tempo de execução desse código pode ser tão pequeno quanto o abaixo

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

Em essência, imitamos a função de primeira classe com os fechamentos representados como par de função / argumentos mais um monte de macroses. O código completo pode ser encontrado aqui .

Viktor Shepel
fonte
7

O principal que vem à mente é o uso de geradores de código. Você gostaria de programar em um idioma diferente que fornecesse a programação funcional e depois gerar o código C a partir disso?

Se essa não é uma opção atraente, você pode abusar do CPP para fazer parte do caminho até lá. O sistema macro deve permitir que você emule algumas idéias de programação funcional. Ouvi dizer que o gcc é implementado dessa maneira, mas nunca verifiquei.

É claro que C pode transmitir funções usando ponteiros de função, os principais problemas são a falta de fechamentos e o sistema de tipos tende a atrapalhar. Você pode explorar sistemas macro mais poderosos que o CPP, como o M4. Acho que, em última análise, o que estou sugerindo é que o verdadeiro C não está pronto para a tarefa sem muito esforço, mas você pode estender o C para que esteja pronto para a tarefa. Essa extensão seria mais parecida com C se você usasse o CPP ou poderia ir para o outro extremo do espectro e gerar código C a partir de outro idioma.

Jason Dagit
fonte
Esta é a resposta mais honesta. Tentar escrever C de maneira funcional é lutar contra o design da linguagem e a própria estrutura.
josiah
5

Se você deseja implementar fechamentos, terá que se familiarizar com a linguagem assembly e trocar / gerenciar pilha. Não recomendando isso, apenas dizendo que é isso que você terá que fazer.

Não tenho certeza de como você lida com funções anônimas no C. Em uma máquina von Neumann, você pode executar funções anônimas no asm.

Paul Nathan
fonte
2

A linguagem Felix é compilada em C ++. Talvez isso possa ser um passo, se você não se importa com C ++.

LennyStackOverflow
fonte
9
⁻¹ porque α) O autor mencionou explicitamente «não C ++», β) O C ++ produzido por um compilador não seria um «C ++ legível por humanos». γ) O padrão C ++ suporta uma programação funcional, sem a necessidade de usar um compilador de um idioma para outro.
Hi-Angel
Depois de criar uma linguagem funcional por meio de macros ou similares, você implica automaticamente que não se importa tanto com C. Essa resposta é válida e correta, porque até o C ++ começou como uma macro party em cima de C. Se você seguir esse caminho longe o suficiente, você terminará com um novo idioma. Em seguida, é apenas uma discussão de back-end.
BitTickler 13/11/2018
1

Bem, algumas linguagens de programação estão escritas em C. E algumas delas suportam funções como cidadãos de primeira classe, as linguagens nessa área são ecl (embbedabble common lisp IIRC), Gnu Smalltalk (gst) (Smalltalk tem blocos), então existem bibliotecas para "fechamentos", por exemplo, na glib2 http://library.gnome.org/devel/gobject/unstable/chapter-signal.html#closure que pelo menos se aproximou da programação funcional. Então, talvez usar algumas dessas implementações para fazer a programação funcional possa ser uma opção.

Bem, ou você pode aprender Ocaml, Haskell, Mozart / Oz ou similares ;-)

Saudações

Friedrich
fonte
Você se importaria de me dizer o que há de tão terrível de errado em usar bibliotecas que, pelo menos, suportam o estilo de programação FP?
Friedrich
1

O modo como desenvolvi a programação funcional em C foi escrever um intérprete de linguagem funcional em C. Chamei-o de Fexl, abreviação de "Function Expression Language".

O intérprete é muito pequeno, compilando até 68K no meu sistema com -O3 ativado. Também não é um brinquedo - estou usando-o para todo o novo código de produção que escrevo para o meu negócio (contabilidade baseada na Web para parcerias de investimento).

Agora, escrevo apenas o código C para (1) adicionar uma função interna que chama uma rotina do sistema (por exemplo, fork, exec, setrlimit etc.) ou (2) otimizar uma função que poderia ser escrita em Fexl (por exemplo, pesquisa para uma substring).

O mecanismo do módulo é baseado no conceito de "contexto". Um contexto é uma função (escrita em Fexl) que mapeia um símbolo para sua definição. Ao ler um arquivo Fexl, você pode resolvê-lo com qualquer contexto que desejar. Isso permite criar ambientes personalizados ou executar código em uma "caixa de areia" restrita.

http://fexl.com

Patrick Chkoreff
fonte
-3

O que você quer tornar funcional, a sintaxe ou a semântica em C? A semântica da programação funcional certamente poderia ser adicionada ao compilador C, mas quando você terminasse, você basicamente teria o equivalente a uma das linguagens funcionais existentes, como Scheme, Haskell, etc.

Seria melhor usar o tempo apenas para aprender a sintaxe daqueles idiomas que suportam diretamente essa semântica.

Barry Brown
fonte
-3

Não sei sobre C. No entanto, existem alguns recursos funcionais no Objective-C, o GCC no OSX também suporta alguns recursos; no entanto, eu recomendo novamente que você comece a usar uma linguagem funcional; há muitos mencionados acima. Eu, pessoalmente, comecei com o esquema, existem alguns livros excelentes, como The Little Schemer, que podem ajudá-lo.

Shiv
fonte