portaldacalheta.pt
  • Principal
  • Ágil
  • Américas
  • Processos Financeiros
  • Design Ux
Processo Interno

Programação declarativa: é uma coisa real?



A programação declarativa é, atualmente, o paradigma dominante de um amplo e diversificado conjunto de domínios, como bancos de dados, modelagem e gerenciamento de configuração.

Em poucas palavras, programação declarativa consiste em instruir um programa sobre o que precisa ser feito, em vez de dizer Como para fazer isso. Na prática, esta abordagem envolve o fornecimento de uma linguagem específica de domínio (DSL) para expressar o que o usuário deseja e protegendo-os das construções de baixo nível (loops, condicionais, atribuições) que materializam o estado final desejado.



Embora esse paradigma seja uma melhoria notável em relação à abordagem imperativa que ele substituiu, eu afirmo que a programação declarativa tem limitações significativas, limitações que exploro neste artigo. Além disso, proponho uma abordagem dupla que captura os benefícios da programação declarativa enquanto supera suas limitações.



EMBARGO : Este artigo surgiu como resultado de uma luta pessoal de vários anos com ferramentas declarativas. Muitas das afirmações que apresento aqui não foram totalmente comprovadas, e algumas até são apresentadas pelo valor de face. Uma crítica adequada da programação declarativa demandaria tempo e esforço consideráveis, e eu teria que voltar e usar muitas dessas ferramentas; meu coração não está em tal empreendimento. O objetivo deste artigo é compartilhar algumas idéias com você, sem rodeios e mostrando o que funcionou para mim. Se você tem dificuldade com ferramentas de programação declarativas, pode encontrar descanso e alternativas. E se você gosta do paradigma e de suas ferramentas, não me leve muito a sério.



Se a programação declarativa funcionar bem para você, Não estou em posição de dizer o contrário .

Você pode amar ou odiar a programação declarativa, mas não pode se dar ao luxo de ignorá-la. Tweet

Os méritos da programação declarativa

Antes de explorarmos os limites da programação declarativa, é necessário entender seus méritos.



Provavelmente, a ferramenta de programação declarativa de maior sucesso é o banco de dados relacional (RDB). Pode até ser a primeira ferramenta declarativa. Em qualquer caso, os RDBs exibem as duas propriedades que considero arquetípicas da programação declarativa:

  • Um idioma específico de domínio (DSL) : a interface universal para bancos de dados relacionais é um DSL chamado Linguagem de consulta estruturada , mais comumente conhecido como SQL.
  • O DSL esconde a camada de nível inferior do usuário : desde então Artigo original de Edgar F. Codd sobre RDBs , está claro que o poder desse modelo é dissociar as consultas desejadas dos loops, índices e caminhos de acesso subjacentes que os implementam.

Antes dos RDBs, a maioria dos sistemas de banco de dados era acessada por meio de código imperativo, que é altamente dependente de detalhes de baixo nível, como a ordem dos registros, índices e caminhos físicos para os próprios dados. Como esses elementos mudam com o tempo, o código geralmente para de funcionar devido a alguma alteração subjacente na estrutura dos dados. O código resultante é difícil de escrever, de depurar, de ler e de manter. Vou arriscar e dizer que a maior parte desse código estava, com toda a probabilidade, longo, cheio de proverbiais ninhos de ratos de condicionais, repetição e sutis bugs dependentes do estado.

Diante disso, os RDBs forneceram um tremendo salto de produtividade para desenvolvedores de sistemas. Agora, em vez de milhares de linhas de código imperativo, você tinha um esquema de dados claramente definido, além de centenas (ou mesmo apenas dezenas) de consultas. Como resultado, os aplicativos precisavam apenas lidar com uma representação de dados abstrata, significativa e duradoura e fazer a interface por meio de uma linguagem de consulta poderosa, porém simples. O RDB provavelmente aumentou a produtividade dos programadores e das empresas que os empregavam em uma ordem de magnitude.

Quais são as vantagens comumente listadas da programação declarativa?

As vantagens da programação declarativa estão listadas abaixo, mas cada uma com um ícone representativo.

Os proponentes da programação declarativa são rápidos em apontar as vantagens. No entanto, até eles admitem que vem com compensações. Tweet
  1. Legibilidade / usabilidade : uma DSL está geralmente mais próxima de uma linguagem natural (como o inglês) do que de um pseudocódigo, portanto, mais legível e também mais fácil de aprender por não-programadores.
  2. Sucinto : muito do boilerplate é abstraído pelo DSL, deixando menos linhas para fazer o mesmo trabalho.
  3. Reuso : é mais fácil criar código que pode ser usado para diferentes fins; algo que é notoriamente difícil ao usar construções imperativas.
  4. Idempotência : você pode trabalhar com estados finais e deixe o programa descobrir por você. Por exemplo, por meio de uma operação de upsert, você pode inserir uma linha se ela não estiver lá ou modificá-la se já estiver lá, em vez de escrever código para lidar com os dois casos.
  5. Recuperação de erro : é fácil especificar uma construção que pare no primeiro erro em vez de ter que adicionar ouvintes de erro para cada erro possível. (Se você já escreveu três callbacks aninhados em node.js, você sabe o que quero dizer.)
  6. Transparência referencial : embora essa vantagem seja comumente associada à programação funcional, ela é válida para qualquer abordagem que minimize o manuseio manual do estado e dependa dos efeitos colaterais.
  7. Comutatividade : a possibilidade de expressar um estado final sem ter que especificar a ordem real em que será implementado.

Embora todas as vantagens acima citadas sejam comumente citadas da programação declarativa, gostaria de condensá-las em duas qualidades, que servirão como princípios orientadores quando proponho uma abordagem alternativa.

  1. Uma camada de alto nível adaptada a um domínio específico : a programação declarativa cria uma camada de alto nível usando as informações do domínio ao qual se aplica. É claro que se estamos lidando com bancos de dados, queremos um conjunto de operações para lidar com dados. A maioria das sete vantagens acima provém da criação de uma camada de alto nível que é precisamente ajustada para um domínio de problema específico.
  2. Poka-yoke (à prova de idiotas) : uma camada de alto nível sob medida para o domínio oculta os detalhes imperativos da implementação. Isso significa que você comete muito menos erros porque os detalhes de baixo nível do sistema simplesmente não estão acessíveis. Esta limitação elimina muitos Aulas de erros do seu código.

Dois problemas com programação declarativa

Nas duas seções a seguir, apresentarei os dois problemas principais da programação declarativa: separação e falta de desdobramento . Toda crítica precisa de seu bicho-papão, então usarei sistemas de modelos HTML como um exemplo concreto das deficiências da programação declarativa.

O problema com DSLs: separação

Imagine que você precise escrever um aplicativo da Web com um número incomum de visualizações. Codificar essas visualizações em um conjunto de arquivos HTML não é uma opção porque muitos componentes dessas páginas mudam.

A solução mais direta, que é gerar HTML concatenando strings, parece tão horrível que você rapidamente procurará uma alternativa. A solução padrão é usar um sistema de template. Embora existam diferentes tipos de sistemas de modelo, iremos contornar suas diferenças para o propósito desta análise. Podemos considerar todos eles semelhantes no sentido de que a principal missão dos sistemas de template é fornecer uma alternativa ao código que concatena strings HTML usando condicionais e loops, assim como os RDBs surgiram como uma alternativa ao código que percorreu os registros de dados.

Suponhamos que optemos por um sistema de modelos padrão; você encontrará três fontes de atrito, que listarei em ordem crescente de importância. A primeira é que o modelo necessariamente reside em um arquivo separado do seu código. Como o sistema de modelos usa uma DSL, a sintaxe é diferente, portanto, não pode estar no mesmo arquivo. Em projetos simples, onde a contagem de arquivos é baixa, a necessidade de manter arquivos de modelo separados pode duplicar ou triplicar a quantidade de arquivos.

Abro uma exceção para modelos Embedded Ruby (ERB), porque Essa são integrados ao código-fonte Ruby. Este não é o caso de ferramentas inspiradas em ERB escritas em outras linguagens, uma vez que esses modelos também devem ser armazenados como arquivos diferentes.

A segunda fonte de atrito é que a DSL tem sua própria sintaxe, diferente da sua linguagem de programação. Conseqüentemente, modificar o DSL (sem falar em escrever o seu próprio) é consideravelmente mais difícil. Para entrar nos bastidores e mudar a ferramenta, você precisa aprender sobre tokenização e análise, o que é interessante e desafiador, mas difícil. Acontece que vejo isso como uma desvantagem.

Você pode perguntar: “Por que diabos você deseja modificar sua ferramenta? Se você estiver fazendo um projeto padrão, uma ferramenta padrão bem escrita deve se adequar ao projeto. ” Talvez sim, talvez não.

Uma DSL nunca tem todo o poder de uma linguagem de programação. Se assim fosse, não seria mais uma DSL, mas sim uma linguagem de programação completa.

Mas não é esse o objetivo de uma DSL? Para não temos todo o poder de uma linguagem de programação disponível, para que possamos obter abstração e eliminar a maioria das fontes de bugs? Talvez sim. Contudo, a maioria DSLs começam simples e, gradualmente, incorporam um número crescente de recursos de uma linguagem de programação até que, de fato, torna-se um . Os sistemas de modelos são um exemplo perfeito. Vamos ver os recursos padrão dos sistemas de modelo e como eles se relacionam aos recursos da linguagem de programação:

  • Substitua o texto dentro de um modelo : substituição de variável.
  • Repetição de um modelo : rotações.
  • Evite imprimir um modelo se uma condição não for atendida : condicionais.
  • Parciais : sub-rotinas.
  • Ajudantes : sub-rotinas (a única diferença com parciais é que os auxiliares podem acessar a linguagem de programação subjacente e deixá-lo sair da camisa de força DSL).

Esse argumento, de que uma DSL é limitada porque simultaneamente cobiça e rejeita o poder de uma linguagem de programação, é diretamente proporcional na medida em que os recursos da DSL são diretamente mapeáveis ​​para os recursos de uma linguagem de programação . No caso do SQL, o argumento é fraco porque a maioria das coisas que o SQL oferece não se parece em nada com o que você encontra em uma linguagem de programação normal. Na outra extremidade do espectro, encontramos sistemas de modelo onde praticamente todos os recursos estão fazendo o DSL convergir para BASIC .

Vamos agora dar um passo atrás e contemplar essas três fontes quintessenciais de atrito, resumidas pelo conceito de separação . Por ser separado, uma DSL precisa estar localizada em um arquivo separado; é mais difícil de modificar (e ainda mais difícil de escrever o seu próprio) e (frequentemente, mas nem sempre) precisa que você adicione, um por um, os recursos que você sente falta de uma linguagem de programação real.

A separação é um problema inerente a qualquer DSL, não importa quão bem projetada.

Agora nos voltamos para um segundo problema de ferramentas declarativas, que é generalizado, mas não inerente.

Outro problema: a falta de desdobramento leva à complexidade

Se eu tivesse escrito este artigo há alguns meses, esta seção teria o nome A maioria das ferramentas declarativas são # @! $ # @! Complexo, mas não sei por quê . No processo de redação deste artigo, encontrei uma maneira melhor de colocá-lo: A maioria das ferramentas declarativas são muito mais complexas do que precisam ser . Vou passar o resto desta seção explicando o porquê. Para analisar a complexidade de uma ferramenta, proponho uma medida chamada de lacuna de complexidade . A lacuna de complexidade é a diferença entre resolver um determinado problema com uma ferramenta e resolvê-lo no nível inferior (presumivelmente, código imperativo simples) que a ferramenta pretende substituir. Quando a primeira solução é mais complexa do que a última, estamos diante da lacuna de complexidade. De mais complexo , Quero dizer mais linhas de código, um código que é mais difícil de ler, mais difícil de modificar e mais difícil de manter, mas não necessariamente tudo isso ao mesmo tempo.

Observe que não estamos comparando a solução de nível inferior com a melhor ferramenta possível, mas sim com não ferramenta. Isso ecoa o princípio médico de 'Primeiro nao faça nenhum mal' .

Os sinais de uma ferramenta com uma grande lacuna de complexidade são:

  • Algo que leva alguns minutos para ser descrito em detalhes ricos em termos imperativos levará horas para codificar usando a ferramenta, mesmo quando você souber como usá-la.
  • Você sente que está constantemente trabalhando com a ferramenta, e não com a ferramenta.
  • Você está lutando para resolver um problema direto que pertence ao domínio da ferramenta que está usando, mas a melhor resposta do Stack Overflow que você encontra descreve um Gambiarra .
  • Quando este problema muito simples pode ser resolvido por um determinado recurso (que não existe na ferramenta) e você vê um problema no Github na biblioteca que apresenta uma longa discussão sobre o referido recurso com +1 s intercalados.
  • Um anseio crônico, com coceira, de abandonar a ferramenta e fazer tudo sozinho dentro de um _ for-loop_.

Eu posso ter sido vítima de emoção aqui, uma vez que os sistemas de modelo não são este complexo, mas esta lacuna de complexidade comparativamente pequena não é um mérito de seu design, mas sim porque o domínio de aplicabilidade é bastante simples (lembre-se, estamos apenas gerando HTML aqui). Sempre que a mesma abordagem é usada para um domínio mais complexo (como gerenciamento de configuração), a lacuna de complexidade pode rapidamente transformar seu projeto em um atoleiro.

Dito isso, não é necessariamente inaceitável que uma ferramenta seja um pouco mais complexa do que o nível inferior que pretende substituir; se a ferramenta produzir um código mais legível, conciso e correto, pode valer a pena. É um problema quando a ferramenta é várias vezes mais complexa do que o problema que ela substitui; isso é totalmente inaceitável. Brian Kernighan afirmou que, “ Controlar a complexidade é a essência da programação de computadores. ”Se uma ferramenta adiciona complexidade significativa ao seu projeto, por que usá-la?

A questão é: por que algumas ferramentas declarativas são muito mais complexas do que o necessário? Acho que seria um erro culpar o design deficiente. Essa explicação geral, um ataque ad hominem generalizado aos autores dessas ferramentas, não é justo. Tem que haver uma explicação mais precisa e esclarecedora.

Meu argumento é que qualquer ferramenta que oferece uma interface de alto nível para abstrair um nível inferior deve desdobrar este nível superior do inferior. O conceito de desdobramento vem da magnum opus de Christopher Alexander, A Natureza da Ordem - em particular o Volume II. Está (desesperadamente) além do escopo deste artigo (para não mencionar meu entendimento) resumir as implicações desse trabalho monumental para o design de software; Acredito que seu impacto será enorme nos próximos anos. Também está além deste artigo fornecer uma definição rigorosa dos processos de desdobramento. Vou usar aqui o conceito em um forma heurística .

Um processo de desdobramento é aquele que, de maneira gradual, cria uma estrutura adicional sem negar a existente. A cada etapa, cada mudança (ou diferenciação, para usar o termo de Alexander) permanece em harmonia com qualquer estrutura anterior, quando a estrutura anterior é, simplesmente, uma sequência cristalizada de mudanças passadas.

Interessantemente suficiente, Unix é um grande exemplo do desdobramento de um nível superior de um inferior. No Unix, dois recursos complexos do sistema operacional, trabalhos em lote e co-rotinas (tubos), são simplesmente extensões de comandos básicos. Por causa de certas decisões de design fundamentais, como tornar tudo um fluxo de bytes, o shell sendo um programa userland e arquivos I / O padrão , O Unix é capaz de fornecer esses recursos sofisticados com complexidade mínima.

Para sublinhar porque estes são excelentes exemplos de desdobramento, gostaria de citar alguns trechos de um Papel de 1979 por Dennis Ritchie, um dos autores do Unix:

Em trabalhos em lote :

… O novo esquema de controle de processo instantaneamente tornou alguns recursos muito valiosos triviais de implementar; por exemplo, processos desanexados (com &) e uso recursivo do shell como um comando. A maioria dos sistemas precisa fornecer algum tipo de batch job submission facilidade e um interpretador de comando especial para arquivos distintos daquele usado interativamente.

Em corrotinas :

A genialidade do pipeline Unix é precisamente que ele é construído a partir dos mesmos comandos usados ​​constantemente de forma simplex.

Essa elegância e simplicidade, eu argumento, vêm de um desdobramento processo. Tarefas em lote e co-rotinas são desdobradas a partir de estruturas anteriores (comandos executados em um shell do ambiente do usuário). Acredito que devido à filosofia minimalista e aos recursos limitados da equipe que criou o Unix, o sistema evoluiu gradativamente e, como tal, foi capaz de incorporar recursos avançados sem virar as costas para os básicos porque não havia recursos suficientes para faça o contrário.

Na ausência de um processo de desdobramento, o nível alto será consideravelmente mais complexo do que o necessário. Em outras palavras, a complexidade da maioria das ferramentas declarativas decorre do fato de que seu alto nível não se desdobra do baixo nível que pretendem substituir.

Essa falta de desdobramento , se você perdoar o neologismo, é rotineiramente justificado pela necessidade de proteger o usuário do nível inferior. Essa ênfase em poka-yoke (protegendo o usuário de erros de baixo nível) vem às custas de uma grande lacuna de complexidade que é autodestrutiva porque a complexidade extra irá gerar novas classes de erros. Para piorar a situação, essas classes de erros nada têm a ver com o domínio do problema, mas com a própria ferramenta. Não iríamos muito longe se descrevermos esses erros como iatrogênico .

As ferramentas de modelagem declarativa, pelo menos quando aplicadas à tarefa de gerar visualizações HTML, são um caso arquetípico de alto nível que vira as costas para o baixo nível que pretende substituir. Como assim? Porque gerar qualquer visão não trivial requer lógica , e sistemas de modelagem, especialmente os sem lógica, banem a lógica pela porta principal e então contrabandeiam parte dela de volta pela porta do gato.

Nota: Uma justificativa ainda mais fraca para uma grande lacuna de complexidade é quando uma ferramenta é comercializada como Magia , ou algo que apenas funciona , a opacidade do nível baixo é considerada uma vantagem porque uma ferramenta mágica sempre deve funcionar sem que você entenda por que ou como. Em minha experiência, quanto mais mágica uma ferramenta pretende ser, mais rápido ela transmuta meu entusiasmo em frustração.

Mas e quanto à separação de interesses? A visão e a lógica não deveriam permanecer separadas? O principal erro, aqui, é colocar a lógica de negócios e a lógica de apresentação no mesmo saco. A lógica de negócios certamente não tem lugar em um modelo, mas a lógica de apresentação existe mesmo assim. A exclusão da lógica dos modelos empurra a lógica da apresentação para o servidor, onde é acomodada de maneira desajeitada. Devo a formulação clara deste ponto a Alexei Boronine, que o apresenta como um excelente caso neste artigo .

Minha impressão é que cerca de dois terços do trabalho de um modelo reside em sua lógica de apresentação, enquanto o outro terço lida com questões genéricas, como concatenar strings, fechar tags, escapar caracteres especiais e assim por diante. Essa é a natureza de baixo nível de duas faces da geração de visualizações HTML. Os sistemas de modelagem lidam adequadamente com a segunda metade, mas não se saem bem com a primeira. Os modelos sem lógica viram as costas para esse problema, forçando você a resolvê-lo de maneira estranha. Outros sistemas de template sofrem porque eles realmente precisam fornecer uma linguagem de programação não trivial para que seus usuários possam realmente escrever lógica de apresentação.

em qual linguagem de programação o Linux é escrito

Resumindo; ferramentas de modelagem declarativa sofrem porque:

  • Se tivessem que se desenvolver a partir de seu domínio de problema, teriam de fornecer meios para gerar padrões lógicos;
  • Uma DSL que fornece lógica não é realmente uma DSL, mas uma linguagem de programação. Observe que outros domínios, como gerenciamento de configuração, também sofrem com a falta de 'desdobramento'.

Gostaria de encerrar a crítica com um argumento que está logicamente desconectado do fio condutor deste artigo, mas que ressoa profundamente em seu núcleo emocional: temos tempo limitado para aprender. A vida é curta e, além disso, precisamos trabalhar. Diante de nossas limitações, precisamos gastar nosso tempo aprendendo coisas que serão úteis e resistirão ao tempo, mesmo em face das rápidas mudanças da tecnologia. É por isso que eu exorto você a usar ferramentas que não apenas forneçam uma solução, mas que realmente lançem uma luz brilhante no domínio de sua própria aplicabilidade. RDBs ensinam sobre dados e Unix ensina conceitos de sistema operacional, mas com ferramentas insatisfatórias que não se desdobram, sempre achei que estava aprendendo os meandros de uma solução abaixo do ideal enquanto permanecia no escuro sobre a natureza do problema pretende resolver.

A heurística que sugiro que você considere é, ferramentas de valor que iluminam seu domínio de problema, em vez de ferramentas que obscurecem seu domínio de problema por trás de supostos recursos .

A abordagem dos gêmeos

Para superar os dois problemas de programação declarativa, que apresentei aqui, proponho uma abordagem dupla:

  • Use uma linguagem específica de domínio de estrutura de dados (dsDSL), para superar a separação.
  • Crie um alto nível que se desdobra do nível inferior para superar a lacuna de complexidade.

dsDSL

Uma estrutura de dados DSL (dsDSL) é uma DSL que é construído com as estruturas de dados de uma linguagem de programação . A ideia central é usar as estruturas de dados básicas que você tem disponíveis, como strings, números, arrays, objetos e funções, e combiná-los para criar abstrações para lidar com um domínio específico.

Queremos manter o poder de declarar estruturas ou ações (alto nível) sem ter que especificar os padrões que implementam essas construções (baixo nível). Queremos superar a separação entre o DSL e nossa linguagem de programação para que possamos usar todo o poder de uma linguagem de programação sempre que precisarmos. Isso não é apenas possível, mas direto por meio de dsDSLs.

Se você me perguntasse há um ano, eu pensaria que o conceito de dsDSL era novo, então um dia, percebi que JSON em si foi um exemplo perfeito dessa abordagem! Um objeto JSON analisado consiste em estruturas de dados que representam entradas de dados declarativamente para obter as vantagens da DSL e, ao mesmo tempo, torná-la fácil de analisar e manipular de dentro de uma linguagem de programação. (Pode haver outros dsDSLs por aí, mas até agora não encontrei nenhum. Se você conhece algum, eu realmente aprecio se mencioná-lo na seção de comentários.)

Como JSON, um dsDSL tem os seguintes atributos:

  1. Ele consiste em um conjunto muito pequeno de funções: JSON tem duas funções principais, parse e stringify.
  2. Suas funções mais comumente recebem argumentos complexos e recursivos: um JSON analisado é um array, ou objeto, que geralmente contém outros arrays e objetos.
  3. As entradas para essas funções estão em conformidade com formulários muito específicos: JSON tem um esquema de validação explícito e estritamente imposto para diferenciar estruturas válidas de inválidas.
  4. Tanto as entradas quanto as saídas dessas funções podem ser contidas e geradas por uma linguagem de programação sem uma sintaxe separada.

Mas os dsDSLs vão além do JSON de muitas maneiras. Vamos criar um dsDSL para gerar HTML usando Javascript. Posteriormente, irei abordar a questão de se essa abordagem pode ser estendida a outras linguagens (spoiler: Isso pode definitivamente ser feito em Ruby e Python, mas provavelmente não em C).

HTML é uma linguagem de marcação composta por tags delimitado por colchetes angulares (< e >). Essas tags podem ter atributos e conteúdos opcionais. Atributos são simplesmente uma lista de atributos de chave / valor e o conteúdo pode ser texto ou outras marcas. Ambos os atributos e conteúdos são opcionais para qualquer tag fornecida. Estou simplificando um pouco, mas é preciso.

Uma maneira direta de representar uma tag HTML em um dsDSL é usando uma matriz com três elementos: - Tag: uma string. - Atributos: um objeto (do tipo simples, chave / valor) ou undefined (se nenhum atributo for necessário). - Conteúdo: uma string (texto), um array (outra tag) ou undefined (se não houver conteúdo).

Por exemplo, Index pode ser escrito como ['a', {href: 'views'}, 'Index'].

Se quisermos embutir este elemento âncora em um div com classe links, podemos escrever: ['div', {class: 'links'}, ['a', {href: 'views'}, 'Index']].

Para listar várias tags html no mesmo nível, podemos agrupá-las em uma matriz:

[ ['h1', 'Hello!'], ['a', {href: 'views'}, 'Index'] ]

O mesmo princípio pode ser aplicado à criação de várias tags dentro de uma tag:

['body', [ ['h1', 'Hello!'], ['a', {href: 'views'}, 'Index'] ]]

Claro, este dsDSL não nos levará muito longe se não gerarmos HTML a partir dele. Precisamos de um generate função que pegará nosso dsDSL e produzirá uma string com HTML. Portanto, se executarmos generate (['a', {href: 'views'}, 'Index']), obteremos a string Index .

A ideia por trás de qualquer DSL é especificar algumas construções com uma estrutura específica que é então passada para uma função. Nesse caso, a estrutura que compõe o dsDSL é este array, que possui de um a três elementos; essas matrizes têm uma estrutura específica. Se generate valida completamente sua entrada (e é fácil e importante validar completamente a entrada, uma vez que essas regras de validação são o análogo preciso da sintaxe de uma DSL), ele dirá exatamente onde você errou com sua entrada. Depois de um tempo, você começará a reconhecer o que distingue uma estrutura válida em um dsDSL, e essa estrutura será altamente sugestiva da coisa subjacente que ela gera.

Agora, quais são os méritos de uma dsDSL em contraposição a uma DSL?

  • Um dsDSL é parte integrante do seu código. Isso leva a contagens de linhas e arquivos mais baixas e a uma redução geral da sobrecarga.
  • dsDSLs são fácil para analisar (portanto, mais fácil de implementar e modificar). A análise é meramente iterar através dos elementos de uma matriz ou objeto. Da mesma forma, dsDSLs são comparativamente fáceis de projetar porque em vez de criar uma nova sintaxe (que todos odiarão), você pode ficar com a sintaxe de sua linguagem de programação (que todos odeiam, mas pelo menos eles já a conhecem).
  • Um dsDSL tem todo o poder de uma linguagem de programação. Isso significa que um dsDSL, quando empregado corretamente, tem a vantagem de ser uma ferramenta de alto e baixo nível.

Agora, a última reivindicação é forte, então vou passar o resto desta seção apoiando-a. O que eu quero dizer com devidamente empregado ? Para ver isso em ação, vamos considerar um exemplo em que queremos construir uma tabela para exibir as informações de uma matriz chamada DATA.

var DATA = [ {id: 1, description: 'Product 1', price: 20, onSale: true, categories: ['a']}, {id: 2, description: 'Product 2', price: 60, onSale: false, categories: ['b']}, {id: 3, description: 'Product 3', price: 120, onSale: false, categories: ['a', 'c']}, {id: 4, description: 'Product 4', price: 45, onSale: true, categories: ['a', 'b']} ]

Em um aplicativo real, DATA será gerado dinamicamente a partir de uma consulta ao banco de dados.

Além disso, temos um FILTER variável que, quando inicializada, será um array com as categorias que queremos mostrar.

Queremos que nossa mesa:

  • Exibir cabeçalhos de tabela.
  • Para cada produto, mostre os campos: descrição, preço e categorias.
  • Não imprima o id campo, mas adicione-o como um id atributo para cada linha. VERSÃO ALTERNATIVA: Adicionar um id atributo para cada tr elemento.
  • Coloque uma aula onSale se o produto está à venda.
  • Classifique os produtos por preço decrescente.
  • Filtre determinados produtos por categoria. Se FILTER é uma matriz vazia, exibiremos todos os produtos. Caso contrário, exibiremos apenas os produtos em que a categoria do produto está contida em FILTER.

Podemos criar a lógica de apresentação que atenda a esse requisito em cerca de 20 linhas de código:

function drawTable (DATA, FILTER) { var printableFields = ['description', 'price', 'categories']; DATA.sort (function (a, b) {return a.price - b.price}); return ['table', [ ['tr', dale.do (printableFields, function (field) { return ['th', field]; })], dale.do (DATA, function (product) { var matches = (! FILTER || FILTER.length === 0) || dale.stop (product.categories, true, function (category) { return FILTER.indexOf (category) !== -1; }); return matches === false ? [] : ['tr', { id: product.id, class: product.onSale ? 'onsale' : undefined }, dale.do (printableFields, function (field) { return ['td', product [field]]; })]; }) ]]; }

Admito que este não é um exemplo direto, no entanto, representa uma visão bastante simples das quatro funções básicas de armazenamento persistente, também conhecido como CRUEL . Qualquer aplicativo da Web não trivial terá visualizações mais complexas do que isso.

Vamos agora ver o que este código está fazendo. Primeiro, ele define uma função, drawTable, para conter a lógica de apresentação do desenho da tabela de produtos. Esta função recebe DATA e FILTER como parâmetros, para que possa ser usado para diferentes conjuntos de dados e filtros. drawTable cumpre o duplo papel de parcial e auxiliar.

var drawTable = function (DATA, FILTER) {

A variável interna, printableFields, é o único lugar onde você precisa especificar quais campos são imprimíveis, evitando repetição e inconsistências em face de mudanças de requisitos.

var printableFields = ['description', 'price', 'categories'];

Em seguida, classificamos DATA de acordo com o preço de seus produtos. Observe que critérios de classificação diferentes e mais complexos seriam simples de implementar, já que temos toda a linguagem de programação à nossa disposição.

DATA.sort (function (a, b) {return a.price - b.price});

Aqui, retornamos um literal de objeto; uma matriz que contém table como seu primeiro elemento e seu conteúdo como o segundo. Esta é a representação dsDSL de

queremos criar.

return ['table', [

Agora criamos uma linha com os cabeçalhos da tabela. Para criar seu conteúdo, usamos continue que é uma função como Array.map , mas que também funciona para objetos. Iremos iterar printableFields e gerar cabeçalhos de tabela para cada um deles:

['tr', dale.do (printableFields, function (field) { return ['th', field]; })],

Observe que acabamos de implementar a iteração, o carro-chefe da geração de HTML, e não precisamos de nenhuma construção DSL; precisamos apenas de uma função para iterar uma estrutura de dados e retornar dsDSLs. Um nativo semelhante, ou função implementada pelo usuário, teria feito o truque também.

Agora itere pelos produtos contidos em DATA.

dale.do (DATA, function (product) {

Verificamos se este produto foi omitido por FILTER. Se FILTER está vazio, iremos imprimir o produto. Se FILTER não estiver vazio, iremos iterar através das categorias do produto até encontrarmos uma que esteja contida em FILTER. Fazemos isso usando dale.stop .

var matches = (! FILTER || FILTER.length === 0) || dale.stop (product.categories, true, function (category) { return FILTER.indexOf (category) !== -1; });

Observe a complexidade do condicional; ele é precisamente adaptado às nossas necessidades e temos total liberdade para expressá-lo porque estamos em uma linguagem de programação ao invés de uma DSL.

Se matches é false, retornamos uma matriz vazia (portanto, não imprimimos este produto). Caso contrário, retornamos um

com seu id e classe apropriados e iteramos por meio de printableFields para, bem, imprimir os campos.

return matches === false ? [] : ['tr', { id: product.id, class: product.onSale ? 'onsale' : undefined }, dale.do (printableFields, function (field) { return ['td', product [field]];

Claro que fechamos tudo o que abrimos. A sintaxe não é divertida?

})]; }) ]]; }

Agora, como incorporamos esta tabela em um contexto mais amplo? Escrevemos uma função chamada drawAll que invocará todas as funções que geram as visualizações. Além de drawTable, também podemos ter drawHeader, drawFooter e outras funções comparáveis, todas as quais retornará dsDSLs .

var drawAll = function () { return generate ([ drawHeader (), drawTable (DATA, FILTER), drawFooter () ]); }

Se você não gosta da aparência do código acima, nada do que eu disser irá convencê-lo. Este é um dsDSL no seu melhor . Você também pode parar de ler o artigo (e deixar um comentário maldoso também, porque você ganhou o direito de fazê-lo se você chegou até aqui!). Mas, falando sério, se o código acima não lhe parece elegante, nada mais neste artigo o fará.

Para aqueles que ainda estão comigo, gostaria de voltar à reivindicação principal desta seção, que é que um dsDSL tem as vantagens de alto e baixo nível :

  • o vantagem do baixo nível reside em escrever código sempre que quisermos, saindo da camisa de força do DSL.
  • o vantagem do alto nível reside em usar literais que representam o que queremos declarar e permitir que as funções da ferramenta convertam isso no estado final desejado (neste caso, uma string com HTML).

Mas como isso é realmente diferente do código puramente imperativo? Acho que, em última análise, a elegância da abordagem dsDSL se resume ao fato de que o código escrito desta forma consiste principalmente em expressões, em vez de declarações . Mais precisamente, o código que usa um dsDSL é quase inteiramente composto por:

  • Literais que mapeiam para estruturas de nível inferior.
  • Invocações de função ou lambdas dentro dessas estruturas literais que retornam estruturas do mesmo tipo.

O código que consiste principalmente em expressões e que encapsula a maioria das instruções dentro de funções é extremamente sucinto porque todos os padrões de repetição podem ser facilmente abstraídos. Você pode escrever código arbitrário, desde que esse código retorne um literal que esteja em conformidade com uma forma não arbitrária muito específica.

Uma outra característica dos dsDSLs (que não temos tempo para explorar aqui) é a possibilidade de usar tipos para aumentar a riqueza e a brevidade das estruturas literais. Explicarei sobre este assunto em um artigo futuro.

Seria possível criar dsDSLs além do Javascript, a One True Language? Acho que é, de fato, possível, desde que a linguagem suporte:

  • Literais para: arrays, objetos (arrays associativos), invocações de função e lambdas.
  • Detecção de tipo de tempo de execução
  • Polimorfismo e tipos de retorno dinâmico

Acho que isso significa que dsDSLs são sustentáveis ​​em qualquer linguagem dinâmica moderna (ou seja: Ruby, Python, Perl, PHP), mas provavelmente não em C ou Java.

Ande e depois deslize: como revelar o alto a partir do baixo

Nesta seção, tentarei mostrar uma maneira de desdobrar uma ferramenta de alto nível de seu domínio. Em suma, a abordagem consiste nas seguintes etapas

  1. Considere de dois a quatro problemas que sejam instâncias representativas de um domínio de problema. Esses problemas deveriam ser reais. Desdobrar o nível alto do baixo é um problema de indução, portanto, você precisa de dados reais para chegar a soluções representativas.
  2. Resolva os problemas com nenhuma ferramenta da maneira mais direta possível.
  3. Afaste-se, dê uma boa olhada em suas soluções e observe os padrões comuns entre elas.
  4. Encontre os padrões de representação (alto nível).
  5. Encontre os padrões de geração (nível baixo).
  6. Resolva os mesmos problemas com sua camada de alto nível e verifique se as soluções estão realmente corretas.
  7. Se você acha que pode facilmente representar todos os problemas com seus padrões de representação, e os padrões de geração para cada uma dessas instâncias produzem implementações corretas, está feito. Caso contrário, volte para a prancheta.
  8. Se surgirem novos problemas, resolva-os com a ferramenta e modifique-a de acordo.
  9. A ferramenta deve convergir assintoticamente para um estado acabado, não importa quantos problemas ela resolva. Em outras palavras, a complexidade da ferramenta deve permanecer constante, ao invés de crescer com a quantidade de problemas que ela resolve.

Agora, o que diabos são padrões de representação e padrões de geração ? Estou feliz que você perguntou. Os padrões de representação são os padrões nos quais você deve ser capaz de expressar um problema que pertence ao domínio que diz respeito à sua ferramenta. É um alfabeto de estruturas que permite que você escreva qualquer padrão que queira expressar dentro de seu domínio de aplicabilidade. Em uma DSL, essas seriam as regras de produção. Vamos voltar ao nosso dsDSL para gerar HTML.

Quebrando um snippet de HTML. A linha

A humilde tag HTML é um bom exemplo de padrões de representação. Vamos dar uma olhada nesses padrões básicos. Tweet

Os padrões de representação para HTML são os seguintes:

  • Uma única tag: ['TAG']
  • Uma única tag com atributos: ['TAG', {attribute1: value1, attribute2: value2, ...}]
  • Uma única tag com conteúdo: ['TAG', 'CONTENTS']
  • Uma única tag com atributos e conteúdos: ['TAG', {attribute1: value1, ...}, 'CONTENTS']
  • Uma única tag com outra tag dentro: ['TAG1', ['TAG2', ...]]
  • Um grupo de tags (autônomo ou dentro de outra tag): [['TAG1', ...], ['TAG2', ...]]
  • Dependendo da condição, coloque uma tag ou nenhuma tag: condition ? ['TAG', ...] : [] / Dependendo de uma condição, coloque um atributo ou nenhum atributo: ['TAG', {class: condition ? 'someClass': undefined}, ...]

Essas instâncias podem ser representadas com a notação dsDSL que determinamos na seção anterior. E isso é tudo de que você precisa para representar qualquer HTML de que possa precisar. Padrões mais sofisticados, como iteração condicional por meio de um objeto para gerar uma tabela, podem ser implementados com funções que retornam os padrões de representação acima, e esses padrões são mapeados diretamente para tags HTML.

Se os padrões de representação são as estruturas que você usa para expressar o que deseja, os padrões de geração são as estruturas que sua ferramenta usará para converter padrões de representação em estruturas de nível inferior. Para HTML, são os seguintes:

  • Valide a entrada (este é, na verdade, um padrão universal de geração).
  • Abra e feche as tags (mas não as tags void, como, que são de fechamento automático).
  • Coloque atributos e conteúdos, escapando de caracteres especiais (mas não o conteúdo das tags).

Acredite ou não, esses são os padrões de que você precisa para criar uma camada dsDSL desdobrável que gere HTML. Padrões semelhantes podem ser encontrados para gerar CSS. De fato, lith faz ambos, em aproximadamente 250 linhas de código.

Resta uma última pergunta a ser respondida: O que quero dizer com ande, então deslize ? Quando lidamos com um domínio de problema, queremos usar uma ferramenta que nos livre dos detalhes desagradáveis ​​desse domínio. Em outras palavras, queremos varrer o nível baixo para baixo do tapete, quanto mais rápido, melhor. o ande, então deslize A abordagem propõe exatamente o oposto: passar algum tempo no nível inferior. Abrace suas peculiaridades e entenda quais são essenciais e quais podem ser evitadas diante de um conjunto de problemas reais, variados e úteis.

Depois de caminhar no nível baixo por algum tempo e resolver problemas úteis, você terá uma compreensão suficientemente profunda de seu domínio. Os padrões de representação e geração surgirão naturalmente; eles são totalmente derivados da natureza do problema que pretendem resolver. Você pode então escrever o código que os emprega. Se funcionarem, você será capaz de passar por problemas em que recentemente teve que passar por eles. Deslizar significa muitas coisas; implica velocidade, precisão e falta de atrito. Talvez mais importante, essa qualidade pode ser sentida; ao resolver problemas com esta ferramenta, você sente que está resolvendo o problema ou que está passando por ele?

Talvez o mais importante sobre uma ferramenta desdobrada não seja o fato de que ela nos livra de ter que lidar com o nível inferior. Em vez disso, ao capturar os padrões empíricos de repetição no nível baixo, uma boa ferramenta de alto nível nos permite entender completamente o domínio da aplicabilidade.

Uma ferramenta desdobrada não resolverá apenas um problema - ela irá esclarecê-lo sobre a estrutura do problema.

Portanto, não fuja de um problema que vale a pena. Primeiro dê a volta e deslize por ele.

Relacionado:Introdução à programação simultânea: um guia para iniciantes

The Rise Of Automated Trading: Machines Trading the S&P 500

Processo Interno

The Rise Of Automated Trading: Machines Trading the S&P 500
Desenvolvedor americano Tondi Butler ganha a quarta bolsa de estudos do ApeeScape

Desenvolvedor americano Tondi Butler ganha a quarta bolsa de estudos do ApeeScape

De Outros

Publicações Populares
As crianças estão passando mais 'tempo sozinhos' com os pais. O que isso significa?
As crianças estão passando mais 'tempo sozinhos' com os pais. O que isso significa?
Um tutorial de fluxo de trabalho de design para desenvolvedores: entregue melhor UI / UX no prazo
Um tutorial de fluxo de trabalho de design para desenvolvedores: entregue melhor UI / UX no prazo
Aprimore o fluxo do usuário - um guia para análise de UX
Aprimore o fluxo do usuário - um guia para análise de UX
Trabalhos a serem realizados: Transforme as necessidades do cliente em soluções de produtos
Trabalhos a serem realizados: Transforme as necessidades do cliente em soluções de produtos
O presidente dos EUA, Donald Trump, critica a ex-NSA Susan Rice por desmascarar funcionários
O presidente dos EUA, Donald Trump, critica a ex-NSA Susan Rice por desmascarar funcionários
 
Todos Juntos Agora - Uma Visão Geral do Design Inclusivo
Todos Juntos Agora - Uma Visão Geral do Design Inclusivo
Métodos de pesquisa UX e o caminho para a empatia do usuário
Métodos de pesquisa UX e o caminho para a empatia do usuário
Como o GWT desbloqueia a realidade aumentada em seu navegador
Como o GWT desbloqueia a realidade aumentada em seu navegador
Um guia para codificação UTF-8 em PHP e MySQL
Um guia para codificação UTF-8 em PHP e MySQL
O que Force Touch significa para IU e UX?
O que Force Touch significa para IU e UX?
Publicações Populares
  • o que torna uma boa api
  • o que é javascript de programação funcional
  • quais são os 8 princípios de design
  • como hackear uma informação de cartão de crédito
  • llc vs c corp vs s corp
  • como escrever um documento de design técnico
Categorias
  • Ágil
  • Américas
  • Processos Financeiros
  • Design Ux
  • © 2022 | Todos Os Direitos Reservados

    portaldacalheta.pt