A programação funcional é um superconjunto de objetos orientados?

26

Quanto mais programação funcional eu faço, mais eu sinto que ela adiciona uma camada extra de abstração que parece como a camada de uma cebola é - tudo abrangendo as camadas anteriores.

Eu não sei se isso é verdade, de acordo com os princípios de OOP com os quais trabalhei há anos, alguém pode explicar como funcional representa ou não com precisão qualquer um deles: Encapsulamento, Abstração, Herança, Polimorfismo

Eu acho que todos podemos dizer, sim, ele tem encapsulamento via tuplas, ou tuplas contam tecnicamente como fato de "programação funcional" ou são apenas uma utilidade da linguagem?

Eu sei que Haskell pode atender ao requisito "interfaces", mas novamente não tenho certeza se o método é um fato funcional? Eu estou supondo que o fato de que os functores tenham uma base matemática, você poderia dizer que esses são definitivamente construídos na expectativa do funcional, talvez?

Por favor, especifique como você acha que funcional cumpre ou não os 4 princípios do POO.

Edit: Eu entendo muito bem as diferenças entre o paradigma funcional e o paradigma orientado a objetos e percebo que existem muitas linguagens multiparadigmas nos dias de hoje que podem fazer as duas coisas. Estou realmente procurando definições de como o fp (pense purista, como o haskell) pode fazer uma das quatro coisas listadas ou por que não pode fazer nenhuma delas. ie "O encapsulamento pode ser feito com fechamentos" (ou, se eu estiver errado nessa crença, indique o motivo).

Jimmy Hoffa
fonte
7
Esses 4 princípios não "fazem" OOP. OOP simplesmente "resolve" aqueles pelo uso de classes, classe hiearchy e suas instâncias. Mas eu também gostaria de uma resposta se houver maneiras de alcançar aqueles em programação funcional.
eufórico
2
@Euphoric Dependendo da definição, ela faz OOP.
precisa saber é o seguinte
2
@KonradRudolph Eu sei que muitas pessoas reivindicam essas coisas e os benefícios que elas trazem como propriedades únicas do OOP. Supondo que "polimorfismo" significa "polimorfismo de subtipo", posso ir com os dois últimos sendo parte integrante do POO. Mas ainda não encontrei uma definição útil de encapsulamento e abstração que exclua abordagens decididamente não relacionadas à OOP. Você pode ocultar detalhes e limitar o acesso a eles, mesmo em Haskell. E o Haskell também possui polimorfismo ad-hoc, não apenas polimorfismo de subtipo - a questão é: o bit "subtipo" importa?
1
@KonradRudolph Isso não torna mais aceitável. Se for o caso, é um incentivo para dar um passo adiante e dar aos que estão espalhando motivos para repensá-lo.
1
A abstração é intrínseca a qualquer programação, pelo menos qualquer programação além do código bruto da máquina. O encapsulamento existe há muito tempo antes da OOP e é intrínseco à programação funcional. Uma linguagem funcional não é necessária para incluir sintaxe explícita para herança ou polimorfismo. Eu acho que isso resulta em 'não'.
Sdenham

Respostas:

44

A programação funcional não é uma camada acima do POO; é um paradigma completamente diferente. É possível fazer POO em um estilo funcional (o F # foi escrito exatamente para esse propósito) e, do outro lado do espectro, existem coisas como Haskell, que rejeita explicitamente os princípios da orientação a objetos.

Você pode fazer o encapsulamento e a abstração em qualquer idioma avançado o suficiente para suportar módulos e funções. OO fornece mecanismos especiais para encapsulamento, mas não é algo inerente ao OO. O ponto de OO é o segundo par que você mencionou: herança e polimorfismo. O conceito é formalmente conhecido como substituição de Liskov, e você não pode obtê-lo sem o suporte no nível da linguagem para programação orientada a objetos. (Sim, é possível falsificá-lo em alguns casos, mas você perde muitas das vantagens que o OO traz para a mesa.)

A programação funcional não se concentra na substituição de Liskov. Ele se concentra em aumentar o nível de abstração e em minimizar o uso de estados e rotinas mutáveis ​​com "efeitos colaterais", que é um termo que programadores funcionais gostam de usar para criar rotinas que realmente fazem algo (em vez de simplesmente calcular algo) som assustador. Mas, novamente, eles são paradigmas completamente separados, que podem ser usados ​​juntos ou não, dependendo da linguagem e da habilidade do programador.

Mason Wheeler
fonte
1
Bem, a herança (nos casos excepcionalmente raros em que é necessário) é alcançável em relação à composição e é mais limpa que a herança em nível de tipo. O polimorfismo é natural, especialmente na presença de tipos polimórficos. Mas é claro que concordo que a FP não tem nada a ver com a OOP e seus princípios.
SK-logic
É sempre possível falsificá-lo - você pode implementar objetos em qualquer idioma que escolher. Concordo com tudo o resto embora :)
Eliot Bola
5
Não acho que o termo "efeitos colaterais" tenha sido cunhado (ou seja usado principalmente) por programadores funcionais.
sepp2k
4
@ sepp2k: Ele não disse que eles inventaram o termo, apenas que eles o usam usando aproximadamente o mesmo tom que normalmente usaria para se referir a crianças que se recusam a sair do gramado.
Aaronaught 3/09/12
16
@Aaronaught não são as crianças no meu gramado que me incomodam, são seus efeitos colaterais sangrentos! Se eles parassem de sofrer mutações por todo o meu gramado, eu não me importaria.
Jimmy Hoffa
10

Acho a seguinte intuição útil para comparar OOP e FP.

Em vez de considerar o FP como um superconjunto do OOP, pense em OOP e FP como duas maneiras alternativas de olhar para um modelo de computação subjacente semelhante no qual você tem:

  1. Alguma operação que é executada,
  2. alguns argumentos de entrada para a operação,
  3. alguns dados / parâmetros fixos que podem influenciar a definição da operação,
  4. algum valor de resultado e
  5. possivelmente um efeito colateral.

No POO, isso é capturado por

  1. Um método que é executado,
  2. os argumentos de entrada de um método,
  3. o objeto no qual o método é chamado, contendo alguns dados locais na forma de variáveis-membro,
  4. o valor de retorno do método (possivelmente nulo),
  5. efeitos colaterais do método.

No FP, isso é capturado por

  1. Um fechamento que é executado,
  2. os argumentos de entrada do fechamento,
  3. as variáveis ​​capturadas do fechamento,
  4. o valor de retorno do fechamento,
  5. os possíveis efeitos colaterais do fechamento (em idiomas puros como Haskell, isso acontece de uma maneira muito controlada).

Com essa interpretação, um objeto pode ser visto como uma coleção de fechamentos (seus métodos), todos capturando as mesmas variáveis ​​não locais (as variáveis ​​de membro do objeto comuns a todos os fechamentos na coleção). Essa visão também é suportada pelo fato de que, em linguagens orientadas a objetos, os fechamentos geralmente são modelados como objetos com exatamente um método.

Penso que os diferentes pontos de vista se originam do fato de que a visão orientada a objetos está centrada nos objetos (os dados), enquanto a visão funcional está centrada nas funções / fechamentos (as operações).

Giorgio
fonte
8

Depende de quem você pede uma definição de POO. Pergunte a cinco pessoas e você provavelmente terá seis definições. A Wikipedia diz :

Tentativas de encontrar uma definição ou teoria de consenso por trás dos objetos não foram muito bem-sucedidas

Portanto, sempre que alguém der uma resposta definitiva, leve-a com um pouco de sal.

Dito isto, há um bom argumento a ser dito que, sim, FP é um superconjunto de POO como paradigma. Em particular, a definição de Alan Kay do termo programação orientada a objetos não contradiz essa noção (mas a de Kristen Nygaard ). Tudo o que Kay estava realmente preocupado era que tudo é um objeto e essa lógica é implementada passando mensagens entre objetos.

Talvez o mais interessante seja a sua pergunta: classes e objetos podem ser pensados ​​em termos de funções e fechamentos retornados por funções (que atuam como classes e construtores de uma só vez). Isso se aproxima muito da programação baseada em protótipo e, de fato, o JavaScript permite fazer exatamente isso.

var cls = function (x) {
    this.y = x;
    this.fun = function () { alert(this.y); };
    return this;
};

var inst = new cls(42);
inst.fun();

(É claro que o JavaScript permite a mutação de valores que são ilegais na programação puramente funcional, mas nem é necessária em uma definição estrita de OOP.)

A questão mais importante, porém, é: esta é uma classificação significativa de OOP? É útil pensar nisso como um subconjunto de programação funcional? Eu acho que na maioria dos casos, não é.

Konrad Rudolph
fonte
1
Eu sinto que pode ser significativo pensar sobre onde a linha deve ser traçada quando eu mudar de paradigma. Se for dito que não há como alcançar o polimorfismo subtípico no FP, então eu nunca vou tentar usar o FP na modelagem de algo que se encaixe bem nele. Se, no entanto, for possível, posso dedicar um tempo para conseguir uma boa maneira de fazê-lo (embora um bom caminho possa não ser possível) ao trabalhar pesadamente em um espaço fp, mas desejar polimorfismo subtipo em alguns espaços de nicho. Obviamente, se a maioria do sistema se encaixa com ele, no entanto, seria melhor usar OOP.
Jimmy Hoffa
6

FP como OO não é um termo bem definido. Existem escolas com definições diferentes, às vezes conflitantes. Se você considera o que eles têm em comum, você se resume a:

  • programação funcional é programação com funções de primeira classe

  • A programação OO é a programação com polimorfismo de inclusão combinado com pelo menos uma forma restrita de sobrecarga resolvida dinamicamente. (Uma observação: nos círculos OO, o polimorfismo é geralmente considerado como polimorfismo de inclusão , enquanto nas escolas de FP geralmente significa polimorfismo paramétrico .)

Tudo o resto está presente em outro lugar ou ausente em alguns casos.

FP e OO são duas ferramentas de construção de abstrações. Cada um deles tem seus próprios pontos fortes e fracos (por exemplo, eles têm uma direção de extensão preferida diferente no problema de expressão), mas nenhum é intrinsecamente mais poderoso que o outro. Você pode criar um sistema OO sobre um kernel FP (o CLOS é um desses sistemas). Você pode usar uma estrutura OO para obter funções de primeira classe (veja como as funções lambda são definidas no C ++ 11, por exemplo).

AProgrammer
fonte
Eu acho que você quer dizer 'funções de primeira classe' em vez de 'funções de primeira ordem'.
dan_waterworth
Errr ... C ++ 11 lambdas dificilmente são funções de primeira classe: cada lambda tem seu próprio tipo ad-hoc (para todos os propósitos práticos, uma estrutura anônima), incompatível com um tipo de ponteiro de função nativa. E std::function, ao qual os ponteiros de função e lambdas podem ser atribuídos, é decididamente genérico, não orientado a objetos. Isso não é surpresa, porque o tipo limitado de polimorfismo da orientação a objetos (polimorfismo de subtipo) é estritamente menos poderoso que o polimorfismo paramétrico (mesmo Hindley-Milner, e muito menos o Sistema F-ômega).
pyon
Eu não tenho muita experiência com linguagens funcionais puristas, mas se você pode definir classes de método estático dentro de fechamentos e passá-las para diferentes contextos, eu diria que você é (estranhamente, talvez) pelo menos a meio caminho lá em opções de estilo funcional. Existem várias maneiras de contornar parâmetros rígidos na maioria dos idiomas.
Erik Reppen
2

Não; OOP pode ser visto como um superconjunto de programação procedural e difere fundamentalmente do paradigma funcional porque possui estado representado nos campos de instância. No paradigma funcional, as variáveis ​​são funções que são aplicadas nos dados constantes para obter o resultado desejado.

Na verdade, você pode considerar a programação funcional um subconjunto de OOP; se você tornar todas as suas classes imutáveis, poderá considerar que possui algum tipo de programação funcional.

m3th0dman
fonte
3
Classes imutáveis ​​não fazem funções de ordem superior, compreendem listas ou encerramentos. Fp não é um subconjunto.
Jimmy Hoffa
1
@Jimmy Hoffa: Você pode simular facilmente uma função de oreder superior criando uma classe que possui um único método que aceita objetos do mesmo tipo ou mais e também retorna um objeto desse tipo semelhante (tipo que possui um método e nenhum campo) . A compreensão da lista não é algo relacionado à linguagem de programação, não ao paradigma (o Smalltalk suporta e é OOP). Os fechamentos estão presentes em C # e também serão inseridos em Java.
M3th0dman 03/09/12
Sim, o C # tem encerramentos, mas isso é por ser um paradigma múltiplo; os fechamentos foram adicionados juntamente com outras partes do FP ao C # (pelo qual sou eternamente grato), mas a presença deles em uma linguagem oop não os torna opacos. Porém, um bom argumento sobre a função de ordem superior: o encapsulamento de um método em uma classe permite o mesmo comportamento.
Jimmy Hoffa
2
Sim, mas se você usar fechamentos para alterar o estado, você ainda programa em um paradigma funcional? O ponto é - paradigma funcional é sobre falta de estado, não sobre funções de alta ordem, recursão ou fechamento.
M3th0dman
pensamento interessante sobre a definição de FP. Vou ter que pensar mais sobre isso, obrigado por compartilhar suas observações.
Jimmy Hoffa
2

Responda:

A Wikipedia tem um ótimo artigo sobre Programação Funcional com alguns dos exemplos que você solicita. @ Konrad Rudolph já forneceu o link para o artigo da OOP .

Não acho que um paradigma seja um superconjunto do outro. São perspectivas diferentes sobre programação e alguns problemas são melhor resolvidos de uma perspectiva e alguns de outra.

Sua pergunta é ainda mais complicada por todas as implementações de FP e OOP. Cada idioma possui suas próprias peculiaridades, relevantes para qualquer boa resposta à sua pergunta.

Divagações cada vez mais tangenciais:

Gosto da ideia de que uma linguagem como Scala tente oferecer o melhor dos dois mundos. Eu me preocupo que isso lhe dê as complicações dos dois mundos também.

Java é uma linguagem OO, mas a versão 7 adicionou um recurso "try-with-resources" que pode ser usado para imitar um tipo de fechamento. Aqui, ele imita a atualização de uma variável local "a" no meio de outra função, sem torná-la visível para essa função. Nesse caso, a primeira metade da outra função é o construtor ClosureTry () e a segunda metade é o método close ().

public class ClosureTry implements AutoCloseable {

    public static void main(String[] args) {
        int a = 1;
        try(ClosureTry ct = new ClosureTry()) {
            System.out.println("Middle Stuff...");
            a = 2;
        }
        System.out.println("a: " + a);
    }

    public ClosureTry() {
        System.out.println("Start Stuff Goes Here...");
    }

    /** Interface throws exception, but we don't have to. */
    public void close() {
        System.out.println("End Stuff Goes Here...");
    }
}

Saída:

Start Stuff Goes Here...
Middle Stuff...
End Stuff Goes Here...
a: 2

Isso pode ser útil para o objetivo de abrir um fluxo, gravar no fluxo e fechá-lo de forma confiável ou simplesmente emparelhar duas funções de uma maneira que você não se esqueça de chamar a segunda depois de fazer algum trabalho entre elas. . Obviamente, é tão novo e incomum que outro programador possa remover o bloco try sem perceber que está quebrando alguma coisa; portanto, atualmente é um tipo de antipadrão, mas interessante que isso possa ser feito.

Você pode expressar qualquer loop nas linguagens mais imperativas como uma recursão. Objetos e variáveis ​​podem ser imutáveis. É possível escrever procedimentos para minimizar os efeitos colaterais (embora eu argumentasse que uma função verdadeira não é possível em um computador - o tempo necessário para executar e os recursos de processador / disco / sistema que consome são efeitos colaterais inevitáveis). Algumas linguagens funcionais podem ser feitas para realizar muitas, senão todas, operações orientadas a objetos. Eles não precisam ser mutuamente exclusivos, embora alguns idiomas tenham limitações (como não permitir nenhuma atualização de variáveis) que impedem certos padrões (como campos mutáveis).

Para mim, as partes mais úteis da programação orientada a objetos são a ocultação de dados (encapsulamento), o tratamento de objetos semelhantes o suficiente (polimorfismo) e a coleta de dados e métodos que operam nesses dados juntos (objetos / classes). A herança pode ser o carro-chefe da OOP, mas para mim é a parte menos importante e menos usada.

As partes mais úteis da programação funcional são imutabilidade (tokens / valores em vez de variáveis), funções (sem efeitos colaterais) e fechamentos.

Não acho que seja orientado a objetos, mas devo dizer que uma das coisas mais úteis na ciência da computação é a capacidade de declarar uma interface e, em seguida, ter várias peças de funcionalidade e dados para implementar essa interface. Também gosto de ter alguns dados mutáveis ​​para trabalhar, então acho que não me sinto totalmente à vontade em linguagens exclusivamente funcionais, mesmo que tente limitar a mutabilidade e os efeitos colaterais em todos os meus projetos de programas.

GlenPeterson
fonte