Como as linguagens de máquina (por exemplo, 0110101000110101
) as linguagens de computador geralmente evoluíram para formas mais altas de abstração, facilitando a compreensão do código quando aplicado a um problema. Assembler era uma abstração sobre código de máquina, C era uma abstração sobre assembler, etc.
O design orientado a objetos parece ser muito bom para nos permitir modelar um problema em termos de objetos, por exemplo, o problema de um sistema de inscrição em um curso universitário pode ser modelado com uma Course
classe, uma Student
classe etc. Então, quando escrevemos a solução em uma linguagem OO, temos classes semelhantes que recebem responsabilidades e que geralmente são úteis para o design, especialmente para modularizar o código. Se eu der esse problema a 10 equipes independentes que o resolverem com um método OO, geralmente as 10 soluções terão as classes relacionadas ao problema em comum. Pode haver muitas diferenças quando você começa a entrar no acoplamento e nas interações dessas classes; portanto, não existe "diferença de representação zero".
Minha experiência com programação funcional é muito limitada (sem uso no mundo real, apenas programas do tipo Hello World). Não estou conseguindo ver como essas linguagens permitem o mapeamento fácil de soluções de FP para problemas (com uma baixa diferença de representação) da mesma forma que as linguagens OO.
Entendo as vantagens do FP em relação à programação simultânea. Mas estou faltando alguma coisa ou a FP não se resume a reduzir uma lacuna representacional (tornando as soluções mais fáceis de entender)?
Outra maneira de perguntar: o código FP de 10 equipes diferentes que resolvem o mesmo problema do mundo real tem muito em comum?
Da Wikipedia sobre Abstração (ciência da computação) (ênfase minha):
Linguagens de programação funcional geralmente exibem abstrações relacionadas a funções , como abstrações lambda (transformando um termo em uma função de alguma variável), funções de ordem superior (parâmetros são funções), abstração de colchetes (transformando um termo em uma função de uma variável).
A lacuna representacional pode ser potencialmente aumentada, porque [alguns] problemas do mundo real não são modelados facilmente com tais abstrações.
Outra maneira que vejo diminuindo a lacuna representacional é rastrear os elementos da solução de volta ao problema. O 0
's e 1
s em código de máquina é muito difícil de rastrear, enquanto que a Student
classe é fácil de rastrear. Nem todas as classes OO rastreiam facilmente para o espaço do problema, mas muitas o fazem.
As abstrações de FP não precisam sempre ser explicadas para descobrir qual parte do espaço do problema está resolvendo (além dos problemas de matemática )?OK - Eu sou bom nessa parte. Depois de examinar muitos outros exemplos, vejo como as abstrações de FP são muito claras para partes do problema que são expressas no processamento de dados.
A resposta aceita para uma pergunta relacionada A UML pode ser usada para modelar um programa Funcional? - diz "Programadores funcionais não têm muito uso para diagramas". Realmente não me importo se é UML, mas me faz pensar em abstrações de FP fáceis de entender / comunicar, se não houver diagramas amplamente usados (assumindo que esta resposta esteja correta). Novamente, meu nível de uso / entendimento de FP é trivial, então não entendo a necessidade de diagramas em programas simples de FP.
O design do OO possui níveis de abstração de função / classe / pacote, com encapsulamento (controle de acesso, ocultação de informações) em cada um, o que facilita o gerenciamento da complexidade. Esses são elementos que permitem ir do problema para a solução e voltar mais facilmente.
Muitas respostas falam de como a análise e o design são feitos no FP de maneira análoga à OO, mas ninguém cita nada de alto nível até agora (paul citou algumas coisas interessantes, mas é de baixo nível). Ontem eu pesquisei bastante no Google e encontrei uma discussão interessante. O seguinte é de refatoração de programas funcionais de Simon Thompson (2004) (ênfase minha)
Ao projetar um sistema orientado a objetos, é dado como certo que o projeto precederá a programação. Os projetos serão gravados usando um sistema como UML, suportado em ferramentas como o Eclipse. Programadores iniciantes podem muito bem aprender uma abordagem de design visual usando sistemas como o BlueJ. O trabalho sobre uma metodologia semelhante para programação funcional é relatado no FAD: Functional Analysis and Design , mas existe pouco outro trabalho. Pode haver várias razões para isso.
Os programas funcionais existentes são de uma escala que não requer design. Muitos programas funcionais são pequenos, mas outros, como o Glasgow Haskell Compiler, são substanciais.
Os programas funcionais modelam diretamente o domínio do aplicativo, tornando o design irrelevante. Embora as linguagens funcionais forneçam uma variedade de abstrações poderosas, é difícil argumentar que elas fornecem todas e apenas as abstrações necessárias para modelar o mundo real.
Programas funcionais são construídos como uma série de protótipos em evolução.
Na tese de doutorado citada acima , os benefícios do uso de metodologias de análise e design (ADM) são destacados independentemente dos paradigmas. Mas é argumentado que as ADMs devem se alinhar ao paradigma de implementação. Ou seja, o OOADM funciona melhor para a programação OO e não é bem aplicado a outro paradigma como o FP. Aqui está uma ótima citação que eu acho que parafraseia o que chamo de gap representacional:
pode-se argumentar longamente sobre qual paradigma fornece o melhor suporte para o desenvolvimento de software, mas obtém-se o pacote de desenvolvimento mais natural, eficiente e eficaz quando se permanece dentro de um único paradigma, desde a descrição do problema até a implementação e entrega.
Aqui está o conjunto de diagramas propostos pelo FAD:
- diagramas de dependência de função que apresentam uma função com aqueles que utiliza em sua implementação;
- diagrama de dependência de tipos que fornece o mesmo serviço para tipos; e,
- diagramas de dependência de módulo que apresentam vistas da arquitetura do módulo do sistema.
Há um estudo de caso na seção 5.1 da tese do FAD, que é um sistema para automatizar a produção de dados relacionados a uma liga de futebol. Os requisitos são 100% funcionais, por exemplo, entrada de resultados de futebol, tabelas de classificação, tabelas de pontuação, tabelas de presença, transferência de jogadores entre equipes, atualização de dados após novos resultados, etc. Nenhuma menção de como o FAD trabalha para resolver requisitos não funcionais está documentada , além de afirmar que "nova funcionalidade deve ser permitida a um custo mínimo", algo quase impossível de testar.
Infelizmente, além do FAD, não vejo referências modernas para linguagens de modelagem (visuais) propostas para o FP. A UML é outro paradigma, por isso devemos esquecer isso.
Respostas:
Os dados básicos estão estruturados da mesma forma em praticamente qualquer paradigma. Você terá a
Student
, aCourse
, etc., seja um objeto, uma estrutura, um registro ou qualquer outra coisa. A diferença com OOP não é como os dados são estruturados, é como as funções são estruturadas.Na verdade, acho que os programas funcionais correspondem muito mais à minha opinião sobre um problema. Por exemplo, para planejar a programação de um aluno para o próximo semestre, você pensa em coisas como listas de cursos que um aluno concluiu, cursos em um programa de graduação, cursos oferecidos neste semestre, cursos para os quais os alunos concluíram pré-requisitos, cursos com horários que não entre em conflito etc.
De repente, não está tão claro qual classe deve criar e armazenar todas essas listas. Ainda menos quando você tem combinações complexas dessas listas. No entanto, você deve escolher uma classe.
No FP, você escreve funções que recebem um aluno e uma lista de cursos e retornam uma lista filtrada de cursos. Você pode agrupar todas essas funções em um módulo. Você não precisa associá-lo a uma aula ou a outra.
Portanto, seus modelos de dados acabam parecendo mais com a maneira como os programadores de POO pensam em seus modelos, antes de serem poluídos por classes que não têm outro objetivo além de fornecer locais convenientes para colocar funções que operam em combinações de outras classes. Não há
CourseStudentFilterList
classes estranhas ou similares que você sempre precise no OOP, mas nunca pense no design inicial.fonte
StudentCourseFilterer
para manter as coisas gerenciávelfunc
(exatamente como você nomeouIFilter
, etc). Em geral, no FP, você o nomeariafunc
ouf
quandosort
oufilter
é uma opção válida! Por exemplo, ao escrever uma função de ordem superior que aceitafunc
como parâmetro.Quando participei da minha aula de Java anos atrás, era esperado que mostrássemos nossas soluções para toda a turma, então pude ver como as pessoas pensam; como eles resolvem problemas logicamente. Eu esperava que as soluções agrupassem em torno de três ou quatro soluções comuns. Em vez disso, observei 30 alunos resolverem o problema de 30 maneiras completamente diferentes.
Naturalmente, à medida que os programadores iniciantes ganham experiência, eles ganham exposição a padrões comuns de software, começam a usar esses padrões em seu código e, em seguida, suas soluções podem se unir em torno de algumas estratégias ótimas. Esses padrões formam uma linguagem técnica pela qual desenvolvedores experientes podem se comunicar.
A linguagem técnica subjacente à programação funcional é a matemática . Consequentemente, os problemas mais adequados para resolver usando a programação funcional são essencialmente problemas matemáticos. Pela matemática, não estou me referindo ao tipo de matemática que você veria nas soluções de negócios, como adição e subtração. Em vez disso, estou falando sobre o tipo de matemática que você pode ver no Math Overflow, ou o tipo de matemática que você veria no mecanismo de pesquisa do Orbitz (escrito em Lisp).
Essa orientação tem algumas ramificações importantes para programadores funcionais que tentam resolver problemas de programação do mundo real:
A programação funcional é mais declarativa do que imperativa; preocupa-se principalmente em dizer ao computador o que fazer, não como fazê-lo.
Os programas orientados a objetos são geralmente criados de cima para baixo. Um design de classe é criado e os detalhes são preenchidos. Os programas funcionais geralmente são criados de baixo para cima, começando com pequenas funções detalhadas que são combinadas em funções de nível superior.
A programação funcional pode ter vantagens na criação de protótipos de lógica complexa, na construção de programas flexíveis que podem mudar e evoluir organicamente e na criação de software onde o design inicial não é claro.
Os programas orientados a objetos podem ser mais adequados para domínios de negócios, porque as classes, as mensagens entre os objetos e os padrões de software fornecem uma estrutura que mapeia o domínio de negócios, captura sua inteligência de negócios e a documenta.
Como todo software prático produz efeitos colaterais (E / S), as linguagens de programação puramente funcionais exigem um mecanismo para produzir esses efeitos colaterais, permanecendo matematicamente puros (mônadas).
Os programas funcionais podem ser mais facilmente comprovados, devido à sua natureza matemática. O sistema de tipos de Haskell pode encontrar coisas em tempo de compilação que a maioria das linguagens OO não consegue.
E assim por diante. Como em muitas coisas na computação, as linguagens orientadas a objetos e as linguagens funcionais têm vantagens e desvantagens.
Algumas linguagens OO modernas adotaram alguns dos conceitos úteis de programação funcional, para que você possa ter o melhor dos dois mundos. O Linq, e os recursos de idioma que foram adicionados para suportá-lo, são um bom exemplo disso.
fonte
SafeString
tipo para representar seqüências de caracteres que escaparam ao HTML - e geralmente controlam mais efeitosGostaria de enfatizar um aspecto que considero importante e que não foi abordado nas outras respostas.
Antes de tudo, acho que o hiato de representação entre problemas e soluções pode estar mais na mente do programador, de acordo com a sua formação e com os conceitos com os quais eles estão mais familiarizados.
OOP e FP analisam dados e operações de duas perspectivas diferentes e fornecem compensações diferentes, como Robert Harvey já apontou.
Um aspecto importante em que diferem é a maneira como permitem estender seu software.
Considere a situação em que você tem uma coleção de tipos de dados e uma coleção de operações; por exemplo, você tem diferentes formatos de imagem e mantém uma biblioteca de algoritmos para processar imagens.
A programação funcional facilita a adição de novas operações ao seu software: você só precisa de uma alteração local no seu código, ou seja, você adiciona uma nova função, que lida com diferentes formatos de dados de entrada. Por outro lado, a adição de novos formatos está mais envolvida: você precisa alterar todas as funções que já implementou (alteração não local).
A abordagem orientada a objetos é simétrica a isso: cada tipo de dados carrega sua própria implementação de todas as operações e é responsável por escolher a implementação correta em tempo de execução (envio dinâmico). Isso facilita a adição de um novo tipo de dados (por exemplo, um novo formato de imagem): você apenas adiciona uma nova classe e implementa todos os seus métodos. Por outro lado, adicionar uma nova operação significa alterar todas as classes que precisam fornecer essa operação. Em muitos idiomas, isso é feito estendendo uma interface e adaptando todas as classes que a implementam.
Essa abordagem diferente da extensão é um dos motivos pelos quais o OOP é mais apropriado para certos problemas em que o conjunto de operações varia com menos frequência do que o conjunto de tipos de dados nos quais essas operações funcionam. Um exemplo típico são GUIs: você tem um conjunto fixo de operações que todos os widgets deve implementar (
paint
,resize
,move
e assim por diante) e uma coleção de widgets que você deseja estender.Portanto, de acordo com essa dimensão, OOP e FP são apenas duas maneiras especulares de organizar seu código. Consulte o SICP , em particular a Seção 2.4.3, Tabela 2.22, e o parágrafo Passagem de mensagem .
Resumindo: no OOP, você usa dados para organizar operações, no FP, você usa operações para organizar os dados. Cada abordagem é mais forte ou mais fraca de acordo com o contexto. Em geral, nenhum dos dois apresenta uma lacuna representacional maior entre problema e solução.
fonte
Other
variante / construtor no seu tipo de dados e um parâmetro de função extra a ser passado para todas as funções para lidar com o outro caso. Obviamente, é estranho / menos natural em relação à solução OOP (envio dinâmico). Da mesma forma, o padrão de visitante é estranho / menos natural que a solução FP (uma função de ordem superior).A maioria das linguagens funcionais não é orientada a objetos. Isso não significa que eles não têm objetos (no sentido de tipos complexos que possuem funcionalidades específicas associadas a eles). Haskell, como java, tem listas, mapas, matrizes, todos os tipos de árvores e muitos outros tipos complexos. Se você observar o módulo Haskell List ou Map, verá um conjunto de funções muito semelhantes aos métodos na maioria dos módulos equivalentes da biblioteca da linguagem OO. Se você inspecionar o código, encontrará até um encapsulamento semelhante, com alguns tipos (ou seus construtores, para ser mais preciso) e funções que podem ser utilizadas apenas por outras funções no módulo.
A maneira como você trabalha com esses tipos no Haskell geralmente não é diferente da maneira OO. Em Haskell eu digo
e em Java você diz
Tomate, tomate. As funções Haskell não estão vinculadas ao objeto, mas estão intimamente associadas ao seu tipo. "Ah, mas" , você diz, "em Java está chamando qual método de comprimento é apropriado para a classe real desse objeto". Surpresa - Haskell também faz polimorfismo com classes de tipos (e outras coisas).
Em Haskell, se eu tiver é - uma coleção de números inteiros - não importa se a coleção é uma lista ou conjunto ou matriz, esse código
retornará uma coleção com todos os elementos dobrados. Polimorfismo significa que a função de mapa apropriada para o tipo específico será chamada.
Esses exemplos, na verdade, não são tipos realmente complexos, mas o mesmo se aplica a problemas mais difíceis. A ideia de que linguagens funcionais não permitem modelar objetos complexos e associar funcionalidades específicas a eles é simplesmente errada.
Não são diferenças importantes e significativas entre o estilo funcional e OO, mas eu não acho que esta resposta tem de lidar com eles. Você perguntou se as linguagens funcionais impedem (ou dificultam) a modelagem intuitiva de problemas e tarefas. A resposta é não.
fonte
O FP realmente se esforça para reduzir o hiato representacional:
Algo que você verá muito nas linguagens funcionais é a prática de construir a linguagem (usando o design de baixo para cima) em uma Linguagem Específica de Domínio Incorporado (EDSL) . Isso permite que você desenvolva um meio de expressar suas preocupações comerciais de uma maneira natural para o seu domínio na linguagem de programação. Haskell e Lisp se orgulham disso.
Parte (se não todos) do que permite essa capacidade é mais flexibilidade e expressividade dentro do (s) idioma (s) de base; com funções de primeira classe, funções de ordem superior, composição de funções e, em alguns idiomas, tipos de dados algébricos (uniões discriminadas por AKA) e a capacidade de definir operadores *, há mais flexibilidade disponível para expressar as coisas do que com o OOP, que significa que você provavelmente encontrará maneiras mais naturais de expressar coisas do domínio do seu problema. (Deve-se dizer algo que muitas linguagens de POO adotaram muitos desses recursos recentemente, se não começando com eles.)
* Sim, em muitos idiomas OOP, você pode substituir os operadores padrão, mas em Haskell, você pode definir novos!
Na minha experiência trabalhando com linguagens funcionais (principalmente F # e Haskell, alguns Clojure e um pouco de Lisp e Erlang), tenho mais facilidade em mapear o espaço do problema para a linguagem do que com o OOP - especialmente com tipos de dados algébricos, que eu acho mais flexível que as aulas. Algo que me impressionou ao começar com o FP, principalmente com Haskell, foi que eu tinha que pensar / trabalhar em um nível um pouco mais alto do que costumava em linguagens imperativas ou OOP; você pode estar se deparando com isso também.
fonte
Como Niklaus Wirth colocou, "Algoritmos + Estruturas de Dados = Programas". A programação funcional é sobre a maneira de organizar algoritmos e não diz muito sobre maneiras de organizar estruturas de dados. De fato, existem linguagens FP tanto com variáveis mutáveis (Lisp) quanto imutáveis (Haskell, Erlang). Se você deseja comparar e contrastar FP com alguma coisa, deve escolher a programação imperativa (C, Java) e declarativa (Prolog).
OOP é, por outro lado, uma maneira de construir estruturas de dados e anexar algoritmos a elas. O FP não impede que você construa estruturas de dados semelhantes às do OOP. Se você não acredita em mim, dê uma olhada nas declarações do tipo Haskell. No entanto, a maioria das linguagens FP concorda que as funções não pertencem às estruturas de dados e devem estar no mesmo espaço para nome. Isso é feito para simplificar a criação de novos algoritmos via composição de funções. De fato, se você sabe qual entrada uma função recebe e qual saída ela produz, por que deveria importar qual estrutura de dados pertence? Por exemplo, por que a
add
função precisa ser chamada em uma instância do tipo Inteiro e transmitida um argumento em vez de simplesmente ser transmitida por dois argumentos Inteiros?Portanto, não vejo por que o FP deva tornar as soluções mais difíceis de entender em geral, e acho que não o faz de qualquer maneira. No entanto, lembre-se de que um programador imperativo experiente certamente encontrará programas funcionais mais difíceis de entender do que os imperativos e vice-versa. Isso é semelhante à dicotomia Windows - Linux, quando as pessoas que investiram 10 anos em se sentirem confortáveis com o ambiente Windows consideram o Linux difícil após um mês de uso, e aqueles que estão acostumados ao Linux não conseguem a mesma produtividade no Windows. Portanto, a "lacuna representacional" de que você está falando é muito subjetiva.
fonte
if you know what input a function takes and what output it produces, why should it matter which data structure it belongs too?
Isso parece muito com coesão, o que para qualquer sistema modular é importante. Eu argumentaria que não saber onde encontrar uma função tornaria mais difícil entender uma solução, o que se deve a uma maior lacuna representacional. Muitos sistemas OO têm esse problema, mas é por causa de um design ruim (baixa coesão). Novamente, a questão do espaço para nome está fora dos meus conhecimentos em FP, então talvez não seja tão ruim quanto parece.