A maioria dos paradigmas de programação que usamos hoje foi estudada matematicamente nos anos 30, usando as idéias do cálculo lambda e da máquina de Turing, que são variantes do modelo de computação universal (sistemas formalizados que podem executar cálculos de uso geral). A tese de Church-Turing mostrou que o cálculo lambda e as máquinas de Turing são funcionalmente equivalentes. Ou seja, estamos falando sobre o fato de que tudo o que pode ser calculado usando uma máquina de Turing também pode ser calculado usando o cálculo lambda e vice-versa.

Existe um equívoco comum de que as máquinas de Turing podem calcular tudo o que pode ser calculado. Existem classes de problemas (por exemplo, o
problema de parar ) que podem ser computados usando máquinas de Turing apenas em alguns casos. Quando a palavra “computável” é usada neste texto, significa “computavelmente por uma máquina de Turing”.
O cálculo Lambda demonstra a abordagem da aplicação de funções em cálculos de cima para baixo. Uma máquina de fita Turing é uma abordagem imperativa (passo a passo) da computação, implementada de baixo para cima.
As linguagens de programação de baixo nível, como código de máquina ou assembler, apareceram na década de 1940 e, no final da década de 1950, surgiram as primeiras linguagens populares de alto nível que implementavam abordagens funcionais e imperativas. Portanto, dialetos da linguagem Lisp ainda são amplamente utilizados, entre eles Clojure, Scheme, AutoLisp e assim por diante. Nos anos 50, idiomas como FORTRAN e COBOL apareceram. Eles são exemplos de linguagens imperativas de alto nível que ainda estão vivas. Embora se deva notar que os idiomas da família C, na maioria das áreas, substituíram o COBOL e o FORTRAN.
As raízes da programação imperativa e funcional estão na matemática formal da computação, elas apareceram diante dos computadores digitais. A Programação Orientada a Objetos (OOP), veio depois, se origina na revolução da programação estrutural, que ocorreu nas décadas de sessenta e setenta do século passado.
O primeiro objeto que eu conhecia foi usado por Ivan Sutherland em seu fatídico aplicativo Sketchpad, criado entre 1961 e 1962, que ele descreveu
neste trabalho em 1963. Os objetos eram caracteres gráficos exibidos na tela do osciloscópio (talvez seja a primeira vez na história do uso de um monitor gráfico de computador) que apóie a herança por meio de delegados dinâmicos, que Ivan Sutherland chamou de "mestres" em seu trabalho. Qualquer objeto pode se tornar um objeto mestre, instâncias adicionais do objeto foram chamadas de "ocorrências". Isso fez do sistema Sketchpad o proprietário da primeira das famosas linguagens de programação que implementaram a herança de protótipo.
A primeira linguagem de programação, comumente conhecida como "orientada a objetos", foi a linguagem Simula, cujas especificações foram desenvolvidas em 1965. Como o Sketchpad, Silmula previa o trabalho com objetos, mas também incluía classes, herança baseada em classes, subclasses e métodos virtuais.
Um método virtual é um método definido em uma classe projetada para ser redefinida por subclasses. Os métodos virtuais permitem que os programas invoquem métodos que podem não existir no momento em que o código é compilado, usando o despacho dinâmico para determinar qual método específico deve ser chamado durante a execução do programa. O JavaScript possui tipos dinâmicos e usa uma cadeia de delegação para determinar qual método chamar.Como resultado, essa linguagem não precisa introduzir o conceito de métodos virtuais para programadores. Em outras palavras, todos os métodos em JavaScript usam despacho em tempo de execução; como resultado, os métodos em JavaScript não precisam ser declarados "virtuais" para oferecer suporte a esse recurso.Opinião do pai do POO sobre POO
"Eu cunhei o termo" orientado a objetos "e posso dizer que não quis dizer C ++." Alan Kay, Conferência OOPSLA, 1997.Alan Kay cunhou o termo "programação orientada a objetos", referindo-se à linguagem de programação Smalltalk (1972). Esse idioma foi desenvolvido por Alan Kay, Dan Ingles e outros funcionários do Xerox PARC Research Center como parte do projeto do dispositivo Dynabook. Smalltalk era mais orientado a objetos que Simula. No Smalltalk, tudo é um objeto, incluindo classes, números inteiros e blocos (fechamentos). A implementação inicial da linguagem, Smalltalk-72, não teve a capacidade de subclasse. Esse recurso apareceu no Smalltalk-76.
Embora o Smalltalk apoiasse as classes e, como resultado, subclasse, o Smalltalk não colocava essas idéias em primeiro plano. Era uma linguagem funcional que Lisp influenciou tanto quanto Simula. De acordo com Alan Kay, tratar as classes como um mecanismo de reutilização de código é um erro. O setor de programação presta muita atenção à criação de subclasses, distraindo as vantagens reais da programação orientada a objetos.
JavaScript e Smalltalk têm muito em comum. Eu diria que o JavaScript é a vingança de Smalltalk no mundo por entender mal os conceitos de OOP. Ambos os idiomas suportam os seguintes recursos:
- Objetos
- Funções e fechamentos de primeira classe.
- Tipos dinâmicos.
- Associação tardia (funções e métodos podem ser substituídos durante a execução do programa).
- POO sem um sistema de herança baseado em classe.
“Lamento ter encontrado o termo“ objetos ”para esse fenômeno há muito tempo, pois seu uso leva ao fato de que muitas pessoas dão importância primária a uma idéia que não é tão importante quanto a principal. A ideia principal é enviar mensagens. ” Alan KayEm uma
correspondência por email de 2003, Alan Kay esclareceu o que tinha em mente quando chamou Smalltalk de "uma linguagem orientada a objetos".
"Para mim, OOP significa apenas mensagens, armazenamento local e proteção, estado de ocultação e ligação muito tarde". Alan KayEm outras palavras, de acordo com as idéias de Alan Kay, os ingredientes mais importantes da POO são os seguintes:
- Mensagens
- Encapsulamento.
- Ligação dinâmica.
É importante notar que Alan Kay, o homem que inventou o termo “POO” e o trouxe às massas, não considerou a herança e o polimorfismo os componentes mais importantes da POO.
A essência do OOP
A combinação de mensagens e encapsulamento serve a vários propósitos importantes:
- Evitando o estado mutável compartilhado de um objeto, encapsulando o estado e isolando outros objetos das alterações locais em seu estado. A única maneira de influenciar o estado de outro objeto é pedir que ele mude (em vez de dar um comando a ele) enviando uma mensagem a ele. As alterações de estado são monitoradas no nível local, celular, o estado não é disponibilizado para outros objetos.
- Separação de objetos um do outro. O remetente da mensagem é fracamente acoplado ao destinatário por meio da API do sistema de mensagens.
- Adaptabilidade e resistência a mudanças durante a execução do programa através de ligação tardia. A adaptação às mudanças durante a execução do programa oferece muitas vantagens significativas, que Alan Kay considerou muito importantes para a OOP.
As fontes de inspiração para Alan Kay, que expressou essas idéias, foram seus conhecimentos de biologia e o que ele sabia sobre a ARPANET (esta é uma versão inicial da Internet). Ou seja, estamos falando de células biológicas e de computadores individuais conectados à rede. Mesmo assim, Alan Kay imaginou como os programas são executados em computadores enormes e distribuídos (a Internet), enquanto computadores individuais agem como células biológicas, trabalhando independentemente com seu próprio estado isolado e trocando dados com outros computadores enviando mensagens.
"Eu percebi que uma metáfora para uma célula ou computador ajudaria a se livrar dos dados [...]". Alan KayDizendo "ajudar a se livrar dos dados", Alan Kay, é claro, estava ciente dos problemas causados pelo estado mutável compartilhado e da forte conectividade causada pelo compartilhamento de dados. Hoje, esses tópicos são amplamente ouvidos. Porém, no final dos anos 60, os programadores da ARPANET estavam descontentes com a necessidade de escolher uma representação de modelo de dados para seus programas antes de desenvolvê-los. Os desenvolvedores queriam se afastar dessa prática, porque, antecipadamente, se dirigindo à estrutura determinada pela apresentação dos dados, é mais difícil mudar alguma coisa no futuro.
O problema era que diferentes maneiras de apresentar os dados exigidos, para acesso a eles, código diferente e sintaxe diferente nas linguagens de programação usadas em algum momento. O Santo Graal aqui seria uma maneira universal de acessar e gerenciar dados. Se todos os dados ficassem iguais para o programa, isso resolveria muitos problemas dos desenvolvedores em relação ao desenvolvimento e manutenção de programas.
Alan Kay tentou "se livrar" da idéia, segundo a qual dados e programas eram, em certo sentido, entidades independentes. Eles não são considerados como tais na Lista ou no Smalltalk. Não há separação entre o que pode ser feito com dados (com valores, variáveis, estruturas de dados etc.) e construções de software como funções. As funções são "cidadãos de primeira classe" e os programas podem mudar durante sua execução. Em outras palavras, o Smalltalk não possui um relacionamento especial e privilegiado com os dados.
Alan Kay, além disso, considerava objetos como estruturas algébricas, que davam garantias definidas e matematicamente comprováveis de seu comportamento.
"Minha formação matemática me permitiu entender que cada objeto pode ter vários modelos algébricos associados a ele, que pode haver grupos inteiros de modelos semelhantes e que eles podem ser muito, muito úteis." Alan KayFicou provado que sim, e isso serviu de base para objetos, como promessas e lentes, além disso, a teoria das categorias foi influenciada por ambos.
A natureza algébrica de como Alan Kay viu os objetos permitiria que os objetos fornecessem verificação formal, comportamento determinístico e melhorassem a capacidade de teste, uma vez que os modelos algébricos são, em essência, operações que obedecem a várias regras na forma de equações.
No jargão dos programadores, “modelos algébricos” são abstrações criadas a partir de funções (operações) que são acompanhadas por certas regras, impostas por testes de unidade que essas funções devem passar (axiomas, equações).
Essas idéias foram esquecidas por décadas na maioria das linguagens orientadas a objetos da família C, incluindo C ++, Java, C # e assim por diante. Mas essas idéias começam a busca pela jornada de retorno, nas versões recentes das linguagens orientadas a objetos mais usadas.
Nesta ocasião, alguém pode dizer que o mundo da programação redescobre os benefícios da programação funcional e fornece argumentos racionais no contexto de linguagens orientadas a objetos.
Como o JavaScript e o Smalltalk, as linguagens orientadas a objetos mais modernas estão se tornando cada vez mais "multiparadigma". Não há razão para escolher entre programação funcional e OOP. Quando analisamos a essência histórica de cada uma dessas abordagens, elas parecem não apenas compatíveis, mas também idéias complementares.
O que, de acordo com os pensamentos de Alan Kay, é o mais importante na OLP?
- Encapsulamento.
- Mensagens
- Ligação dinâmica (a capacidade dos programas de se desenvolverem e se adaptarem às mudanças durante sua execução).
O que é insignificante no POO?
- Classes.
- Herança baseada em classe.
- Relação particular a objetos, funções ou dados.
- Palavra-chave
new
. - Polimorfismo.
- Digitação estática.
- Atitude para com as classes como "tipos".
Se você conhece Java ou C #, pode pensar que a digitação estática ou o polimorfismo são os ingredientes mais importantes da OOP, mas Alan Kay prefere lidar com padrões de comportamento universais na forma algébrica. Aqui está um exemplo escrito em Haskell:
fmap :: (a -> b) -> fa -> fb
Essa é a assinatura do functor universal de
map
, que funciona com os tipos indefinidos
a
e
b
, aplicando a função de
a
em
b
no contexto do functor
a
para criar o functor
b
. "Functor" é uma palavra do jargão matemático, cujo significado é reduzido para "suporte da operação de exibição". Se você está familiarizado com o método
[].map()
no JavaScript, já sabe o que isso significa.
Aqui estão alguns exemplos de JavaScript:
// isEven = Number => Boolean const isEven = n => n % 2 === 0; const nums = [1, 2, 3, 4, 5, 6]; // map `a => b` `a` ( `this`) // `b` // `a` `Number`, `b` `Boolean` const results = nums.map(isEven); console.log(results); // [false, true, false, true, false, true]
O método
.map()
é universal, no sentido de que
a
e
b
podem ser de qualquer tipo, e esse método lida com uma situação semelhante sem problemas, pois matrizes são estruturas de dados que implementam as leis algébricas dos functores. Os tipos para
.map()
não importam, pois esse método não tenta trabalhar com os valores correspondentes diretamente. Em vez disso, ele usa uma função que espera e retorna valores dos tipos correspondentes que estão corretos do ponto de vista do aplicativo.
// matches = a => Boolean // `a` , const matches = control => input => input === control; const strings = ['foo', 'bar', 'baz']; const results = strings.map(matches('bar')); console.log(results); // [false, true, false]
Pode ser difícil expressar corretamente e totalmente o relacionamento de tipos universais em linguagens como TypeScript, mas é muito simples no sistema de tipos Hindley-Milner usado em Haskell, que suporta tipos mais altos (tipos de tipos).
A maioria dos sistemas de tipos impõe restrições muito fortes para permitir a livre expressão de idéias dinâmicas e funcionais, como composição de funções, composição livre de objetos, expansão de objetos durante a execução do programa, uso de combinadores, lentes e assim por diante. Em outras palavras? tipos estáticos geralmente dificultam a criação de software usando métodos de construção.
Se o seu sistema de tipos tiver muitas restrições (como no TypeScript ou Java), para atingir os mesmos objetivos, você precisará escrever um código mais complexo do que ao usar idiomas com uma abordagem mais livre para digitar. Isso não significa que o uso de tipos estáticos seja uma ideia infeliz ou que todas as implementações de tipos estáticos tenham as mesmas limitações. Por exemplo, encontrei muito menos problemas ao trabalhar com o sistema do tipo Haskell.
Se você é fã de tipos estáticos e não é contra restrições, desejo sete pés sob a quilha. Mas se você achar que algumas das idéias expressas aqui são difíceis de implementar devido ao fato de não ser fácil digitar funções obtidas pela composição de outras funções e estruturas algébricas compostas, culpe o sistema de tipos e não a idéia. Motoristas gostam das comodidades que os SUVs de quadro oferecem, mas ninguém reclama que não voa. Para voar, você precisa de um veículo com mais graus de liberdade.
Se as restrições simplificam o seu código - ótimo! Mas se as restrições forçarem você a escrever código mais complexo, talvez algo esteja errado com essas restrições.
O que é um "objeto"?
A palavra "objeto", ao longo do tempo, adquiriu muitas conotações secundárias de significado. O que chamamos de "objetos" em JavaScript são simplesmente tipos de dados compostos, sem nenhuma sugestão de programação baseada em classe ou as idéias de Alan Kay para transmissão de mensagens.
Em JavaScript, esses objetos podem suportar, e geralmente suportam, encapsulamento, passagem de mensagens, separação de comportamento por métodos, até polimorfismo usando subclasses (embora usando uma cadeia de delegação em vez de despacho baseado em tipo).
Alan Kay queria se livrar da diferença entre o programa e seus dados. O JavaScript, até certo ponto, alcança esse objetivo colocando os métodos de objeto no mesmo local que as propriedades que armazenam os dados. Qualquer propriedade, por exemplo, pode ser atribuída a qualquer função. Você pode construir o comportamento do objeto dinamicamente e alterar o conteúdo semântico do objeto durante a execução do programa.
Um objeto é apenas uma estrutura de dados composta e não precisa de nada de especial para ser considerado um objeto. No entanto, programar usando objetos não leva ao fato de que esse código acaba sendo "orientado a objetos", assim como o uso de funções não torna o código "funcional".
OOP não é mais um OOP real
Como o conceito de "objeto" nas linguagens de programação modernas significa muito menos do que Alan Kay quis dizer, eu uso a palavra "componente" em vez da palavra "objeto" para descrever as regras deste OOP. Muitos objetos pertencem e são controlados diretamente por algum código JavaScript de terceiros, mas os componentes devem encapsular seu próprio estado e controlá-lo.
Aqui está o que é OOP real:
- Programação usando componentes (Alan Kay os chama de "objetos").
- O estado do componente deve ser encapsulado.
- Para comunicação entre entidades, é usado o sistema de mensagens.
- Os componentes podem ser adicionados, modificados e substituídos no tempo de execução.
A maioria dos comportamentos de objetos pode ser definida de maneira universal usando estruturas de dados algébricas. Não há necessidade de herança. Os componentes podem reutilizar comportamentos de funções públicas e importar módulos, sem precisar tornar seus dados públicos.
Manipular objetos em JavaScript ou usar herança baseada em classe não significa que alguém esteja envolvido na programação OOP. Mas o uso de componentes de tais maneiras - significa. Mas é muito difícil livrar-se das idéias estabelecidas sobre os termos, então talvez devamos deixar o termo "OOP" e chamar o que os "componentes" acima são usados como "Programação Orientada a Mensagens (MOP)"? Usaremos o termo "MOP" abaixo para falar sobre programação orientada a mensagens.
Por acaso, a palavra em inglês “mop” é traduzida como “mop” e, como você sabe, é usada para restaurar a ordem.
Como é um bom MOP?
A maioria dos programas modernos possui uma certa interface de usuário (interface do usuário, interface do usuário) responsável por interagir com o usuário, algum código envolvido no gerenciamento do estado do aplicativo (dados do usuário) e código que funciona com o sistema ou é responsável pela troca de dados com a rede.
Para dar suporte à operação de cada um desses sistemas, processos de longa duração, como ouvintes de eventos, podem ser necessários. Aqui você precisará do estado do aplicativo - para armazenar algo como informações sobre conexões de rede, sobre o estado das coisas com os controles da interface e sobre o próprio aplicativo.
Um bom MOP significa que, em vez de todos os sistemas terem acesso ao estado um do outro e poderem controlá-los diretamente, eles interagem entre si por meio de mensagens. Quando o usuário clica no botão "Salvar", a mensagem
"SAVE"
pode ser enviada. O componente do aplicativo de gerenciamento de estado pode interpretar essa mensagem e redirecioná-la para o processador responsável pela atualização do estado (como uma função de redutor puro). Talvez após a atualização do estado, o componente responsável pelo gerenciamento do estado
"STATE_UPDATED"
a mensagem
"STATE_UPDATED"
o componente da interface do usuário, que, por sua vez, interpreta o estado, decide quais partes da interface precisam ser atualizadas e transmite o estado atualizado aos subcomponentes responsáveis por trabalhar com elementos de interface específicos.
Enquanto isso, o componente responsável pelas conexões de rede pode monitorar a conexão do usuário com outro computador na rede, ouvir mensagens e enviar uma visão atualizada do estado para salvá-lo na máquina remota. Esse componente é responsável por trabalhar com mecanismos de rede, sabe se a conexão funciona ou não e assim por diante.
Sistemas de aplicativos semelhantes não devem conhecer os detalhes de suas outras partes. Eles devem se preocupar apenas em resolver seus próprios problemas. Os componentes do sistema podem ser desmontados e montados como construtor. Eles implementam interfaces padronizadas, o que significa que eles podem interagir entre si. Desde que os requisitos conhecidos para a interface dos componentes sejam atendidos, esses componentes podem ser substituídos por outros, com as mesmas interfaces, mas fazendo a mesma coisa de maneira diferente ou executando, recebendo as mesmas mensagens, algo completamente diferente. Você pode alterar um componente para outro, mesmo durante a execução do programa - isso não interromperá o trabalho.
Os componentes de um sistema de software nem precisam estar no mesmo computador. O sistema pode ser descentralizado. O armazenamento em rede pode colocar dados em um sistema de armazenamento descentralizado como o
IPFS , como resultado, o usuário é independente da integridade de uma máquina específica, o que garante a segurança de seus dados. Com essa abordagem, os dados são armazenados e protegidos de maneira confiável contra intrusos.
A OLP, em parte, ficou sob a influência das idéias da ARPANET, e um dos objetivos deste projeto era criar uma rede descentralizada que fosse resistente a ataques como um ataque nuclear.
Um bom sistema MOP pode ser caracterizado por um nível semelhante de estabilidade usando componentes que suportam troca a quente enquanto o aplicativo está em execução. Ele poderá continuar funcionando se o usuário trabalhar com ele a partir de um telefone celular e estiver fora da cobertura da rede devido ao fato de ter entrado no túnel. Se um furacão interromper o fornecimento de energia de um dos datacenters em que seus servidores estão localizados, ele também continuará funcionando.
Chegou a hora do mundo do software se libertar de um experimento de herança baseado em classe sem êxito e adotar os princípios matemáticos e científicos que estavam na vanguarda da OOP.
É hora de nós, desenvolvedores, criar programas mais flexíveis, estáveis e bonitos, usando uma combinação harmoniosa de MOP e programação funcional.
A propósito, o acrônimo "MOP" já está em uso, descrevendo "Monitoring Oriented Programming", mas esse conceito, diferente do OOP, simplesmente desaparece silenciosamente.
Portanto, não desanime se o termo "MOP" não parecer uma palavra do jargão dos programadores. Apenas arrume seu POO com os princípios acima mencionados.
