Ultimamente, tenho me interessado por alguns dos conceitos de programação funcional. Eu tenho usado OOP por algum tempo agora. Eu posso ver como eu criaria um aplicativo bastante complexo no OOP. Cada objeto saberia como fazer as coisas que o objeto faz. Ou qualquer coisa que a classe dos pais faça também. Então eu posso simplesmente dizer Person().speak()
para fazer a pessoa falar.
Mas como faço coisas semelhantes em programação funcional? Eu vejo como as funções são itens de primeira classe. Mas essa função faz apenas uma coisa específica. Eu simplesmente teria um say()
método flutuando e o chamaria com um equivalente de Person()
argumento para saber que tipo de coisa está dizendo alguma coisa?
Para que eu possa ver as coisas simples, como eu faria o comparável entre OOP e objetos na programação funcional, para que eu possa modularizar e organizar minha base de código?
Para referência, minha principal experiência com OOP é Python, PHP e alguns C #. As linguagens que estou vendo que possuem recursos funcionais são Scala e Haskell. Embora eu esteja inclinado para Scala.
Exemplo básico (Python):
Animal(object):
def say(self, what):
print(what)
Dog(Animal):
def say(self, what):
super().say('dog barks: {0}'.format(what))
Cat(Animal):
def say(self, what):
super().say('cat meows: {0}'.format(what))
dog = Dog()
cat = Cat()
dog.say('ruff')
cat.say('purr')
Respostas:
O que você realmente está perguntando aqui é como fazer o polimorfismo em linguagens funcionais, ou seja, como criar funções que se comportam de maneira diferente com base em seus argumentos.
Observe que o primeiro argumento para uma função é tipicamente equivalente ao "objeto" no OOP, mas em linguagens funcionais você geralmente deseja separar funções dos dados, portanto o "objeto" provavelmente será um valor de dados puro (imutável).
As linguagens funcionais em geral fornecem várias opções para alcançar o polimorfismo:
Como exemplo, aqui está uma implementação Clojure do seu problema usando vários métodos:
Observe que esse comportamento não exige que nenhuma classe explícita seja definida: mapas regulares funcionam bem. A função de despacho (: type neste caso) pode ser qualquer função arbitrária dos argumentos.
fonte
Esta não é uma resposta direta, nem é necessariamente 100% precisa, pois não sou especialista em linguagem funcional. Mas, em ambos os casos, compartilharei com você minha experiência ...
Cerca de um ano atrás, eu estava em um barco semelhante ao seu. Eu fiz C ++ e C # e todos os meus projetos sempre foram muito pesados no OOP. Eu ouvi falar sobre linguagens FP, li algumas informações on-line, folheei o livro F #, mas ainda não consegui entender como uma linguagem FP substitui o OOP ou pode ser útil em geral, pois a maioria dos exemplos que eu vi era simples demais.
Para mim, o "avanço" veio quando eu decidi aprender python. Eu baixei o python, fui para a página inicial do projeto euler e comecei a fazer um problema após o outro. O Python não é necessariamente uma linguagem FP e você certamente pode criar classes nele, mas comparado ao C ++ / Java / C #, ele possui muito mais construções FP, portanto, quando comecei a brincar com ele, tomei uma decisão consciente de não definir uma classe, a menos que eu precise absolutamente.
O que eu achei interessante no Python foi o quão fácil e natural era pegar funções e "costurá-las" para criar funções mais complexas e, no final, seu problema ainda era resolvido chamando uma única função.
Você apontou que, ao codificar, deve seguir o princípio de responsabilidade única e isso é absolutamente correto. Mas apenas porque a função é responsável por uma única tarefa, não significa que ela possa fazer apenas o mínimo necessário. No FP, você ainda tem níveis de abstração. Portanto, suas funções de nível superior ainda podem fazer "uma" coisa, mas podem delegar para funções de nível inferior para implementar detalhes mais finos de como essa "única" coisa é alcançada.
A chave do FP, no entanto, é que você não tem efeitos colaterais. Contanto que você trate o aplicativo como uma simples transformação de dados com um conjunto definido de entradas e um conjunto de saídas, é possível escrever um código FP que realizaria o que você precisa. Obviamente, nem todos os aplicativos se encaixam bem nesse molde, mas, quando você começar a fazê-lo, ficará surpreso com o número de aplicativos que se encaixam. E é aqui que acho que Python, F # ou Scala brilham porque eles oferecem construções de FP, mas quando você precisa se lembrar do seu estado e "apresentar efeitos colaterais", sempre pode recorrer a técnicas de OOP verdadeiras e experimentadas.
Desde então, escrevi um monte de código python como utilitários e outros scripts auxiliares para o trabalho interno, e alguns deles foram reduzidos bastante longe, mas lembrando os princípios básicos do SOLID, a maior parte desse código ainda era muito sustentável e flexível. Assim como no OOP, sua interface é uma classe e você move as classes ao refatorar e / ou adicionar funcionalidades, no FP, você faz exatamente a mesma coisa com as funções.
Na semana passada, comecei a codificar em Java e, desde então, quase diariamente sou lembrado de que, quando estou em OOP, tenho que implementar interfaces declarando classes com métodos que substituem funções, em alguns casos eu poderia conseguir o mesmo em Python usando um uma expressão lambda simples, por exemplo, 20 a 30 linhas de código que escrevi para varrer um diretório, teria de 1 a 2 linhas em Python e nenhuma classe.
FP em si são idiomas de nível superior. No Python (desculpe, minha única experiência em FP), eu poderia reunir compreensão de lista dentro de outra compreensão de lista com lambdas e outras coisas lançadas e a coisa toda seria apenas 3-4 linhas de código. Em C ++, eu conseguia absolutamente fazer a mesma coisa, mas como o C ++ é de nível inferior, eu teria que escrever muito mais código do que 3-4 linhas e, à medida que o número de linhas aumenta, meu treinamento em SRP entra em ação e eu começava. pensando em como dividir o código em partes menores (ou seja, mais funções). Mas, no interesse da manutenção e da ocultação de detalhes de implementação, eu colocaria todas essas funções na mesma classe e as tornaria privadas. E aí está ... Acabei de criar uma classe enquanto em python eu teria escrito "return (.... lambda x: .. ....)"
fonte
Em Haskell, o mais próximo que você tem é "classe". Essa classe, embora não seja a mesma que a Java e C ++ , funcionará para o que você deseja neste caso.
No seu caso, é assim que o seu código será exibido.
Então você pode ter tipos de dados individuais adaptando esses métodos.
EDIT: - Antes que você possa se especializar, diga para Dog, você precisa informar ao sistema que Dog é animal.
EDIT: - Para Wilq.
Agora, se você quiser usar say em uma função say foo, precisará informar ao haskell que foo só pode funcionar com Animal.
Agora, se você chamar foo com cachorro, latirá, se você chamar com gato, miará.
Agora você não pode ter nenhuma outra definição de função, digamos. Se o say for chamado com algo que não seja animal, ele causará erro de compilação.
fonte
Linguagens funcionais usam 2 construções para obter polimorfismo:
Criar código polimórfico com eles é completamente diferente de como o OOP usa métodos virtuais e de herança. Embora ambos estejam disponíveis no seu idioma favorito de POO (como C #), a maioria dos idiomas funcionais (como Haskell) aumenta para onze. É raro funcionar como não genérico e a maioria das funções tem funções como parâmetros.
É difícil explicar dessa maneira e exigirá muito tempo para aprender dessa nova maneira. Mas para fazer isso, você precisa esquecer completamente o POO, porque não é assim que funciona no mundo funcional.
fonte
realmente depende do que você deseja realizar.
se você só precisa de uma maneira de organizar o comportamento com base em critérios seletivos, pode usar, por exemplo, um dicionário (tabela de hash) com objetos de função. em python, pode ser algo como:
note, no entanto, que (a) não existem 'instâncias' de cães ou gatos e (b) você terá que acompanhar o 'tipo' de seus objetos.
Como por exemplo:
pets = [['martin','dog','grrrh'], ['martha', 'cat', 'zzzz']]
. então você poderia fazer uma compreensão da lista como[animals[pet[1]]['say'](pet[2]) for pet in pets]
fonte
Às vezes, os idiomas OO podem ser usados no lugar dos idiomas de baixo nível para interagir diretamente com uma máquina. C ++ Com certeza, mas mesmo para C # existem adaptadores e afins. Embora escrever código para controlar peças mecânicas e ter controle minucioso da memória seja mantido o mais próximo possível do nível mais baixo possível. Mas se esta pergunta está relacionada ao software orientado a objetos atual, como linha de negócios, aplicativos da Web, IOT, serviços da Web e a maioria dos aplicativos usados em massa, então ...
Resposta, se aplicável
Os leitores podem tentar trabalhar com uma Arquitetura Orientada a Serviços (SOA). Ou seja, DDD, N-Camada, N-Camada, Hexagonal, qualquer que seja. Não vi um aplicativo de negócios grande usar eficientemente o OO "tradicional" (Active-Record ou Rich-Models), conforme descrito nas décadas de 70 e 80 na última década. (Ver nota 1)
A falha não está no OP, mas há alguns problemas com a pergunta.
O exemplo que você fornece é simplesmente demonstrar polimorfismo, não é um código de produção. Às vezes, exemplos exatamente assim são tomados literalmente.
No FP e SOA, os dados são separados da lógica de negócios. Ou seja, Data e Logic não andam juntos. A lógica entra em Serviços e os Dados (Modelos de Domínio) não têm comportamento polimórfico (consulte a Nota 2).
Serviços e funções podem ser polimórficos. No FP, você passa funções como parâmetros para outras funções, em vez de valores. Você pode fazer o mesmo nos idiomas OO com tipos como Callable ou Func, mas ele não funciona de forma desenfreada (consulte a Nota 3). No FP e SOA, seus modelos não são polimórficos, apenas seus serviços / funções. (Ver nota 4)
Há um caso de codificação incorreta nesse exemplo. Não estou falando apenas da corda vermelha "latidos de cachorro". Também estou falando sobre o próprio CatModel e DogModel. O que acontece quando você deseja adicionar uma ovelha? Você precisa entrar no seu código e criar um novo código? Por quê? No código de produção, eu preferiria ver apenas um AnimalModel com suas propriedades. Na pior das hipóteses, um AmphibianModel e um FowlModel se suas propriedades e manipulação forem muito diferentes.
Isso é o que eu esperaria ver no atual idioma "OO":
Como você muda de Classes em OO para Programação Funcional? Como outros já disseram; Você pode, mas na verdade não. O objetivo do exposto acima é demonstrar que você nem deveria usar Classes (no sentido tradicional do mundo) ao executar Java e C #. Depois de escrever o código em uma arquitetura orientada a serviços (DDD, em camadas, em camadas, hexagonal, qualquer que seja), você estará um passo mais perto do Functional porque separa seus dados (modelos de domínio) das funções lógicas (serviços).
OO Language um passo mais perto do FP
Você pode ir um pouco mais longe e dividir seus serviços SOA em dois tipos.
Classe opcional tipo 1 : Serviços comuns de implementação de interface para pontos de entrada. Estes seriam pontos de entrada "impuros" que podem chamar outras funcionalidades "Puras" ou "Impuras". Podem ser seus pontos de entrada de uma API RESTful.
Classe opcional tipo 2 : serviços puros de lógica comercial. Essas são classes estáticas que possuem a funcionalidade "pura". No FP, "Puro" significa que não há efeitos colaterais. Ele não define explicitamente Estado ou Persistência em lugar algum. (Ver nota 5)
Portanto, quando você pensa em Classes em Linguagens Orientadas a Objetos, sendo usadas em uma Arquitetura Orientada a Serviços, ele não apenas beneficia seu Código OO, mas também começa a tornar a Programação Funcional muito fácil de entender.
Notas
Nota 1 : O design orientado a objeto "Rich" ou "Active-Record" original ainda existe. Há MUITO código legado assim quando as pessoas estavam "fazendo o certo" há uma década ou mais. A última vez que vi esse tipo de código (feito corretamente) era de um videogame Codebase em C ++, onde eles controlavam a memória com precisão e tinham um espaço muito limitado. Para não dizer FP e arquiteturas orientadas a serviços são bestas e não devem considerar hardware. Mas eles priorizam a capacidade de mudar constantemente, manter, ter tamanhos de dados variáveis e outros aspectos. Nos videogames e na IA da máquina, você controla os sinais e dados com muita precisão.
Nota 2 : Os modelos de domínio não possuem comportamento polimórfico nem dependências externas. Eles são "isolados". Isso não significa que eles tenham que ser 100% anêmicos. Eles podem ter muita lógica relacionada à sua construção e alteração de propriedade mutável, se aplicável. Veja DDD "Objetos de Valor" e Entidades de Eric Evans e Mark Seemann.
Nota 3 : Linq e Lambda são muito comuns. Mas quando um usuário cria uma nova função, ele raramente usa Func ou Callable como parâmetros, enquanto no FP seria estranho ver um aplicativo sem funções seguindo esse padrão.
Nota 4 : Não confunda Polimorfismo com Herança. Um CatModel pode herdar o AnimalBase para determinar quais propriedades um animal normalmente possui. Mas, como mostro, modelos como este são um cheiro de código . Se você vir esse padrão, considere dividi-lo e transformá-lo em dados.
Nota 5 : Funções puras podem (e fazem) aceitar funções como parâmetros. A função de entrada pode ser impura, mas pode ser pura. Para fins de teste, sempre seria puro. Mas na produção, embora seja tratado como puro, pode conter efeitos colaterais. Isso não muda o fato de que a função pura é pura. Embora a função de parâmetro possa ser impura. Não é confuso! : D
fonte
Você poderia fazer algo assim .. php
fonte
$whostosay
torna-se o tipo de objeto que determina o que é executado. O acima pode ser modificado para aceitar adicionalmente outro parâmetro,$whattosay
para que um tipo que o suporte (por exemplo'human'
) possa utilizá-lo.