Programação Funcional não é apenas Programação Imperativa disfarçada?

8

Um vídeo do YouTube que eu estava assistindo explicou as diferenças entre programação imperativa e funcional, demonstrando como os números de 1para 10são resumidos em Java e em Haskell, respectivamente.

Em Java, você deve declarar explicitamente cada etapa e atribuir o resultado de cada etapa a uma variável - algo como o seguinte

int total = 0;
     for (int i = 1; i <= 10; i++){
         total = total + i;
     }
return total;

Em Haskell, você pode simplesmente dizer:

sum(1..10)

Minha pergunta é: obviamente há algo acontecendo no fundo de uma linguagem funcional, e que algo deve ser algum tipo de processo imperativo. Parece que as linguagens funcionais são realmente apenas um tipo de API de idioma imperativo. Por exemplo, eu posso criar parte de uma linguagem funcional definindo um método sum(int start, int end)em Java. Eu realmente criei um novo tipo de linguagem ali, ou acabei de definir um conjunto de chamadas de método Imperative que ocultam instruções imperativas de você?

Espero que esteja claro o que estou lutando para entender.

CodyBugstein
fonte
11
Pelo motivo exato em que você declara, o exemplo da função soma não é bom.
David Richerby
É por isso que é chamado de estilo , em oposição a um novo tipo de computador.
user253751

Respostas:

10

Se descascarmos o açúcar sintático na frente e a geração de código na parte de trás e compararmos o que acontece entre a conversão de código fonte e código em execução para linguagens imperativas , como C ou Java, com linguagens funcionais como ML ou OCaml, geralmente encontraremos as seguintes diferenças em quê, por que e como.

Mutável vs. imutável

Com a programação funcional, tende-se a usar valores imutáveis, o que significa que não precisamos nos preocupar se um valor mudar por um meio externo à nossa função atual. Quando usado corretamente, remove todos os problemas relacionados aos efeitos colaterais .

Foco: Dados versus Função.

Quando se pensa em codificar como imperativo, primeiro se pensa em estruturas de dados e depois em quais métodos eles precisam. Ao trabalhar com programação funcional, primeiro se pensa em quais funções são necessárias e depois cria os tipos de dados necessários. A maioria dos tipos de dados é de lista lenta (think stream ou lista infinita) ou uniões discriminadas . Quando a união discriminada é recursiva, você cria instantaneamente uma árvore ou gráfico sem precisar escrever todo o código ambulante.

Genéricos / Polimorfismo paramétrico

Este é interessante e, se os meus fatos estiverem corretos, foi inventado com programação funcional e depois transplantado para programação imperativa. Então, se você gosta de genéricos, agradeça aos designers de linguagem funcional.

Transparência referencial / paralelização

Devido à transparência referencial, o código funcional pode ser transportado com mais facilidade para a computação paralela .

Função de ordem superior / composicionalidade

Como as funções podem criar novas funções e retornar funções, a criação de novas funções é baseada em outras funções e é tão fácil quanto criar novas expressões em vez de escrever novos métodos inteiros. Isso leva a morfismos que são muito úteis se o problema que você está resolvendo pode ser superado com a matemática. Fazer transformações de conjunto, como SQL e atualizações, é muito mais fácil com a programação funcional. Como observou a Wandering Logic , é aqui que as linguagens de programação funcional se destacam.

Digitação: Estática versus inferência .

Como os tipos são inferidos em vez de serem definidos pelo programador durante a gravação, mais verificações podem ser feitas para garantir a correção do código e, muitas vezes, as funções serão tornadas genéricas em vez de serem de um tipo definido.

Correspondência de padrão vs instrução switch

Ao combinar correspondência com uniões discriminadas, sua correspondência é verificada para garantir que você tenha coberto todos os resultados. Quantas vezes você teve um erro em tempo de execução porque perdeu um caso com uma instrução switch.

Guy Coder
fonte
“Ao trabalhar com programação funcional, primeiro se pensa em quais funções são necessárias e depois cria os tipos de dados necessários”: depende do que se está modelando. Tipos de dados especialmente recursivos são um hot spot no FP tanto quanto as funções.
Hibou57
“Ao combinar a correspondência com uniões discriminadas, sua correspondência é verificada para garantir que você tenha coberto todos os resultados”: enquanto (e como uma nota lateral), as declarações de caso também são verificadas, com Ada, o que é imperativo. Ser imperativo não exclui a correspondência de padrões ou a verificação de cobertura.
Hibou57
@ Hibou57 Comentários agradáveis. Eu estaria interessado em ver sua resposta.
Guy Coder
Concordo que se pode pensar nos dados primeiro antes das funções, como observado por Hibou57. Então, deixe-me esclarecer isso um pouco. Eu costumo usar linguagens funcionais para o que eles são bons e usar linguagens imperativas para o que eles são bons, e usar linguagens lógicas para o que eles são bons, etc., e não tenho problemas ao usar dois ou mais idiomas por um problema, separando a tarefa entre os idiomas. Então, quando eu usar uma linguagem funcional I tendem a pensar funções de primeira e quando eu preciso de dados persistentes que tendem a pensar de dados em primeiro lugar, e a lógica que eu uso uma linguagem lógica, etc.
Guy Coder
11
Para a declaração de caso Ada, aqui a referência: 5.4 Declarações de caso (para a última revisão até esta data). Em particular, observe o texto sobre “capa” / “cobertura” e os gostos.
Hibou57
9

Uma linguagem de programação funcional é notável pelo que proíbe . Proíbe modificar uma variável ou estrutura de dados existente. Você pode programar em um "estilo funcional" em algumas linguagens de programação imperativas, mas a linguagem não o protegerá contra a modificação acidental de uma variável ou estrutura de dados existente. Por exemplo, aqui está uma versão recursiva e funcional do sumJava:

int sum(int start, int end) {
  if (start > end) return 0;
  else {
    int total = start + sum(start+1, end);
    return total;
  }
}

Onde as linguagens de programação funcional se destacam é que todas elas têm funções "de primeira classe". Ou seja: uma função pode ser usada como um valor, assim como um número inteiro ou uma estrutura de dados. Algumas linguagens imperativas também têm funções de primeira classe (principalmente C e C ++), mas em Java você precisa falsificá-lo com um objeto com um único método (geralmente chamado apply()ou algo assim). As funções de primeira classe permitem generalizar a sumfunção para um função que reduz qualquer operador:

interface IntegerOperator {
  int apply(int a, int b);
}

int sum(IntegerOperator op, int start, int end) {
  if (start > end) return 0;
  else {
    int total = op.apply(start, sum(op, start+1, end))
    return total;
  }
}

class AdditionOperator implements IntegerOperator {
  int apply(int a, int b) { return a+b; }
}

int total = sum(new AdditionOperator(), 1, 10);

Se eu estivesse escrevendo em uma linguagem de programação funcional, saberia que toda função é realmente uma função (ela retorna o mesmo valor toda vez que é chamada com os mesmos parâmetros), então eu saberia isso em:

int total1 = sum(new ReallyStrangeOperator(), 1, 10);
int total2 = sum(new ReallyStrangeOperator(), 1, 10);

total1e total2têm o mesmo valor (por exemplo, eu poderia otimizar a segunda chamada para sum), enquanto que em uma linguagem de programação imperativa não tenho idéia se ReallyStrangeOperator(1,2)retorna o mesmo valor sempre ou não.

Lógica Errante
fonte
2
Obrigado pela resposta realmente informativa. Aceitei o outro, mas poderia facilmente aceitar o seu e escrever esse comentário no outro.
CodyBugstein