socialgekon.com
  • Principal
  • Talento Ágil
  • De Outros
  • Europa
  • Processo Interno
Processo Interno

Programação declarativa realmente existe?

A programação declarativa é atualmente o paradigma dominante de um conjunto de diversos e extensos domínios como: bancos de dados, templates e gerenciamento de configuração.

Em poucas palavras, programação declarativa consiste em dizer a um programa o que este tem que fazer em vez de contar Como devo fazer isso. Na prática, essa abordagem significa fornecer uma linguagem de domínio específico (DSL). Lenguage específico de domínio ) para expressar o que este o usuário deseja e protege-os de arquiteturas ou construções de baixo nível (loops, condicionais, atribuições) que materializam o estado final desejado.

Embora esse paradigma seja uma grande melhoria em relação à abordagem imperativa que substituiu, eu poderia dizer que a programação declarativa tem limitações significativas, limitações que pretendo explorar neste artigo. Além disso, proponho uma abordagem dupla que engloba os benefícios da programação declarativa, enquanto supera suas limitações.



EMBARGO : Este artigo foi escrito como resultado de uma luta pessoal de vários anos com ferramentas declarativas. Muitas das coisas que ele argumentou não foram totalmente comprovadas e algumas são apresentadas como valor de face. Uma crítica adequada da programação declarativa levaria muito tempo, esforço e eu teria que voltar e usar muitas dessas ferramentas, mas meu coração não estaria no esforço. O objetivo deste artigo é compartilhar meus pensamentos com você, sem problemas, vou apenas mostrar o que funcionou para mim. Se você lutou com altos e baixos com as ferramentas de programação declarativa, pode encontrar algum conforto e algumas alternativas. E se você gosta desse paradigma e de suas ferramentas, não preste muita atenção em mim.

Se a programação declarativa funciona para você Não tenho o direito de te dizer o contrário .

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

Você pode odiar ou amar 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 explorar as limitações da programação declarativa, acho necessário entender seus méritos.

Sem dúvida, a ferramenta de programação declarativa mais eficaz é o banco de dados relacional (RDB). banco de dados relacional ) Pode até ser considerada a primeira ferramenta declarativa. De qualquer forma, RDB tem duas propriedades que você considerou arquetípicas da programação declarativa:

  • Uma linguagem específica de domínio (DSL) : a interface universal para bancos de dados relacionais é esta DSL: Structured Query Language , mais conhecido como SQL.
  • DSL esconde camadas de baixo nível do usuário : desde que Edgar F. Codd publicou seu artigo na RDB Ficou bem claro que o objetivo desse modelo é desassociar as consultas desejadas dos loops, índices e caminhos subjacentes que as implementam.

Antes do RDB, a maioria dos sistemas de banco de dados podia ser acessada por meio de código imperativo, que é extremamente dependente de detalhes de baixo nível como registros, índices e caminhos físicos para os próprios dados. Como esses elementos mudam depois de um certo tempo, o código pode parar de funcionar devido a alguma mudança subjacente na estrutura dos dados. O código que resta como resultado é difícil de escrever, depurar, ler e difícil de manter. Provavelmente, o código era muito longo, cheio de condicionais muito longas, repetições e talvez alguns bugs que não eram perceptíveis, mas eram dependentes do estado.

Se for esse o caso, os RDBs forneceram um grande salto de produtividade para os desenvolvedores de sistema. Agora, em vez de ter milhares de linhas de código imperativo, você pode ter um esquema de dados definido e também centenas (ou mesmo algumas) de consultas. Como resultado, os aplicativos precisam apenas lidar com uma representação abstrata, significativa e durável dos dados; e interligando-o através de uma linguagem de consulta poderosa e ao mesmo tempo simples. O RDB provavelmente ajudou a aumentar a produtividade dos programadores, bem como das empresas que os empregaram, em uma ordem de magnitude.

Quais são os benefícios mais comuns da programação declarativa?

Os proponentes da programação declarativa são sempre rápidos em mencionar suas vantagens. No entanto, mesmo essas pessoas admitem que isso vem com certos compromissos.

Os proponentes da programação declarativa são sempre rápidos em mencionar suas vantagens. No entanto, mesmo essas pessoas admitem que isso vem com certos compromissos. Tweet
  1. Legibilidade / usabilidade : um DSL está geralmente mais próximo de uma linguagem natural (como inglês ou espanhol) do que de um pseudocódigo e, portanto, é mais fácil de ler e aprender por pessoas que não são programadores.
  2. Concisão : A maior parte da tablatura é extraída por DSL, deixando menos linhas para fazer o mesmo trabalho.
  3. Reuso : é mais fácil criar um código que pode ser usado para diferentes fins; algo que todos sabem que é extremamente difícil ao usar construções imperativas.
  4. Idempotencia : você pode trabalhar com estados finais e deixe o programa fazer o resto. Por exemplo, em uma operação de upsert você pode inserir uma linha se ela não estiver lá ou você pode modificá-la se já estiver lá, em vez de escrever um código que cuida de ambos os casos.
  5. Erro de recuperação : É fácil identificar uma construção que irá parar no primeiro erro, em vez de ter que adicionar listas de erros para cada erro possível. (Se você já escreveu 3 chamadas de retorno ou callbacks aninhado em node.js para que você saiba o que ele falou.)
  6. Transparência Referencial Embora essa vantagem esteja mais associada à programação funcional, a verdade é que ela é válida para qualquer abordagem que minimize a manipulação manual do estado e seja baseada em efeitos colaterais.
  7. Comutatividade - A capacidade de expressar um estado final sem especificar a ordem real em que será implementado.

Embora as vantagens acima sejam muito comuns quando se fala em programação declarativa, vou resumi-las em duas qualidades que servirão como princípios orientadores ao propor uma abordagem alternativa. 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. Camada de alto nível personalizada para um domínio específico - A programação declarativa cria uma camada de nível superior usando informações do domínio ao qual se aplica. Definitivamente, se estamos usando um banco de dados, é porque queremos um conjunto de operações para lidar com os dados. A maioria dos 7 benefícios mencionados acima vem da criação de uma camada de alto nível que foi projetada especialmente para um problema de domínio.
  2. Poka-yoke (infalível) - Uma camada de alto nível personalizada para um domínio oculta os detalhes imperativos da implementação. O que significa que você cometerá menos erros porque os detalhes de baixo nível do sistema não estão acessíveis. Esta limitação remove muitos aulas erros em seu código.

Dois problemas com programação declarativa

Nas próximas duas seções, discutirei dois dos principais problemas da programação declarativa: separação Y falta de implantação . Cada revisão precisa de um bicho-papão e é por isso que usarei um modelo de sistema HTML como um exemplo exato das deficiências da programação declarativa.

O problema com separações DSL

Imagine que você precise escrever um aplicativo da web com um número não trivial de visitas. Codificar essas visitas em um conjunto de arquivos HTML não é uma opção, pois muitos componentes dessas páginas mudam.

A solução mais rápida, que seria gerar HTML por meio de strings de concatenação, na verdade parece horrível para você e você começará a procurar uma alternativa imediatamente. A solução padrão é usar um sistema de modelos. Embora existam diferentes tipos de sistemas de template, colocaremos suas diferenças de lado nesta discussão por enquanto. Podemos considerá-los semelhantes quando se trata de sistemas de template para fornecer uma alternativa à codificação de concatenação de strings HTML, usando condicionais e loops. Assim como os RDBs surgiram como uma alternativa para a vinculação de loop por meio de registros de dados.

Suponha que escolhemos o sistema de template padrão; Nele você encontrará três fontes de atrito, que nomearei em ordem crescente, dependendo de sua importância. A primeira seria que o modelo deve residir em um arquivo separado do seu código. Visto que o sistema de template usa uma DSL, a sintaxe é diferente e, portanto, não pode estar no mesmo arquivo. Em projetos simples, onde o número de arquivos não é alto, a necessidade de manter os arquivos de modelo separados é duas ou três vezes mais importante porque excede o número de arquivos.

Vou abrir uma exceção com os modelos Ruby Embutido (ERB por sua sigla em Inglês Modelos de Ruby incorporados ) devido a que Essa eles são integrados ao código-fonte do Ruby. Mas não é o caso das ferramentas inspiradas no ERB, escritas em outras linguagens, pois esses templates devem ser salvos em arquivos diferentes.

A segunda fonte de atrito é que o DSL tem sua própria sintaxe, diferente daquela da linguagem de programação. É por isso que modificar o DSL (e até mesmo escrever um seu próprio) é muito mais difícil. Para ir por baixo da corda e mudar a ferramenta, você teria que aprender sobre tokenização e análise (análise), que são considerados interessantes e desafiadores, mas muito difíceis. Na minha opinião, isso é uma desvantagem.

Como um DSL pode ser visualizado? Não é fácil, mas você poderia dizer que uma DSL é uma boa camada limpa em uma construção de baixo custo.

Como um DSL pode ser visualizado? Não é fácil, mas você poderia dizer que uma DSL é uma boa camada limpa em uma construção de baixo custo. Tweet

Você pode estar se perguntando: “Por que diabos você deseja modificar sua ferramenta? Se você estiver fazendo um projeto padrão, uma ferramenta bem escrita deve funcionar para você. ' Talvez sim ou talvez não.

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

Mas não é esse o objetivo de uma DSL? O fato de não temos todo o controle de uma linguagem de programação já disponível e assim podemos obter uma abstração e também eliminar mais fontes de insetos . Talvez sim. No entanto, o a maioria de DSLs começam simples e gradualmente começam a incorporar um grande número de recursos de uma linguagem de programação até que sejam se tornar um . Os sistemas de modelos são o exemplo perfeito. Agora, vamos examinar os recursos padrão dos sistemas de modelo e como eles são mapeados para os recursos de uma linguagem de programação:

  • Substitua o texto dentro de um modelo : substituição de variável.
  • Repetindo um modelo : rotações.
  • Evite imprimir um modelo se uma condição não se adequar : condicionais.
  • Parcial : sub-rotinas.
  • Ajudantes : sub-rotinas (a única diferença com parciais é que os auxiliares podem acessar a linguagem de programação subjacente e dar a você a liberdade de sair do DSL).

Quando dizem que uma DSL é limitada porque é simultaneamente ganância e rejeita o controle de uma linguagem de programação, isso é diretamente proporcional ao fato de que os recursos DSL são mapeáveis ​​para recursos de uma linguagem de programação . No que diz respeito ao SQL, este não é o caso porque a maioria das coisas que o SQL oferece não se parece com o que você normalmente encontraria em uma linguagem de programação. E, na outra extremidade do espectro, encontramos sistemas de modelo onde praticamente todos os recursos estão fazendo com que o DSL convirja para BASIC .

Agora vamos voltar e contemplar essas três fontes de atrito por excelência que se resumem no conceito de separação . Por ser separado, uma DSL precisa estar localizada em um arquivo separado; É difícil de modificar (e ainda mais difícil de escrever) e (às vezes, mas nem sempre) é necessário que você adicione, uma a uma, as características que você precisa de uma linguagem de programação real.

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

E agora um segundo problema com ferramentas declarativas, que é geral, mas não inerente.

Outro problema: a falta de implantação leva à complexidade

Se eu tivesse escrito um artigo há alguns meses, esta seção se chamaria A maioria das ferramentas declarativas são # @! $ # @! Complexo mas não sei porque . No processo de redação deste artigo, encontrei uma maneira melhor de colocá-lo: A maioria das ferramentas declarativas são mais complexas do que deveriam . Nesta seção, explicarei o porquê. Para analisar a complexidade de uma ferramenta, gosto de propor uma medida chamada de margem de complexidade . A margem de complexidade é a diferença entre resolver um determinado problema com uma ferramenta ou resolvê-lo no nível baixo (código supostamente simples e imperativo) que a ferramenta está tentando substituir. Quando a solução anterior é mais complexa do que a próxima, então nos encontramos com a margem de complexidade. Quando eu digo mais complexo Quero dizer mais linhas de código, um código que é mais difícil de ler, modificar e manter, mas não necessariamente tudo ao mesmo tempo.

Observe que não estamos comparando a solução de baixo nível com a melhor ferramenta que existe, em vez disso, estamos comparando com Nenhum ferramenta. Isso se assemelha ao princípio médico de 'Primeiro, você não vai doer' .

Sinais de uma ferramenta com uma grande margem de complexidade:

  • Algo que deve levar alguns minutos para ser descrito em grandes detalhes em termos imperativos levará horas para codificar usando a ferramenta, mesmo se você souber como usar a ferramenta.
  • Você começa a sentir que está trabalhando com a ferramenta, e não com ela.
  • Você acha difícil resolver um problema simples que claramente pertence ao domínio da ferramenta que está usando, mas a melhor resposta que você encontra em Stack Overflow a única coisa que descreve é uma alternativa .
  • Quando este simples problema pode ser resolvido por um determinado recurso (que não existe na ferramenta) e você vê um problema do Github na biblioteca que apresenta uma grande discussão desse recurso com vários +1 intercaladas.
  • Como você se sente ao sentir o desejo incontrolável de largar totalmente a ferramenta e fazer tudo sozinho _ por loop_.

Talvez eu me empolgue com emoções porque os sistemas de modelos não são assim complicado, mas esta relativamente pequena margem de complexidade não é um mérito do seu design, em vez disso, o domínio de aplicabilidade é bastante simples (lembre-se de que estamos gerando HTML). Sempre que a mesma abordagem é usada para um domínio mais complexo (como gerenciamento de configuração), a margem de complexidade pode rapidamente transformar seu projeto em um atoleiro.

Agora, há exceções em que não é totalmente inaceitável que uma ferramenta seja um pouco mais complexa do que o nível inferior que você deseja substituir; se a ferramenta fornecer 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 está substituindo, isso é totalmente inaceitável. Brian Kernighan é famoso por ter afirmado que: “ Essa complexidade de controle é a essência da programação de computadores. ”Se uma ferramenta só adiciona complexidade significativa ao seu projeto, por que usá-la?

E nos perguntamos: por que algumas ferramentas declarativas são mais complexas do que o necessário? Acho que seria um erro culpar um projeto ruim. É uma explicação tão geral, um argumento tão ad hominem para os autores dessas ferramentas que não é justo. Tem que haver outra explicação, mais precisa e informada.

E agora um pouco de Origami! Uma ferramenta com uma interface de alto nível para um baixo nível abstrato deve exibir o nível alto a partir da parte inferior.

E agora um pouco de Origami! Uma ferramenta com uma interface de alto nível para um baixo nível abstrato deve exibir o nível alto a partir da parte inferior. Tweet

Meu argumento é que toda ferramenta que oferece uma interface de alto nível para extrair um implantar aquele nível mais alto do fundo. O conceito de implantar foi apresentado na obra de arte de Christopher Alexander: 'The Nature of Order' ou A Natureza da Ordem - em particular o Volume II. Está (desesperadamente) além do escopo deste artigo (para não mencionar, no meu entendimento) resumir as implicações desse trabalho monumental sobre design de software; embora eu acredite que nos próximos anos seu impacto será enorme. Também está fora do escopo deste artigo fornecer uma definição detalhada dos processos de implantação. Mas vou usar o conceito de forma heurística .

Um processo de implantação consiste em criar uma estrutura sem negar a que existe. Em cada etapa, cada mudança (ou diferenciação, para citar os termos de Alexander) permanece em harmonia com qualquer estrutura anterior, quando a estrutura anterior é simplesmente uma sequência cristalizada de mudanças passadas.

O mais interessante é que Unix é um excelente exemplo de 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. Para certas decisões de design, como tornar tudo um fluxo de bytes, o Shell um programa do usuário e os Arquivos de E / S são padrão , O Unix é capaz de fornecer esses recursos tão sofisticados quanto o mínimo de complexidade. Para surpreender por que esses exemplos de exibição são excelentes, vou citar alguns trechos de um Artigo de 1979 De Dennis Ritchie, um dos autores do Unix:

Sobre trabalhos em lote :

… O novo esquema de processo instantaneamente tornou alguns recursos valiosos triviais de implementar; por exemplo, um processo separado (com &) e um uso recursivo de shell como comando. A maioria dos sistemas precisa fornecer algum tipo de aplicación de trabajo en lote um comando de intérprete especial e capacidade para arquivos diferentes daqueles usados ​​interativamente.

Sobre co-rotinas :

A melhor coisa sobre os pipes Unix é precisamente que eles são construídos a partir dos mesmos comandos usados ​​constantemente de uma forma simples.

Os pioneiros do Unix, Dennis Ritchie e Ken Thompson, criaram uma grande demonstração de implantação em seu sistema operacional. E também nos salvaram de um futuro distópico com apenas o Windows como opção.

Os pioneiros do Unix, Dennis Ritchie e Ken Thompson, criaram uma grande demonstração de implantação em seu sistema operacional. E também nos salvaram de um futuro distópico com apenas o Windows como opção. Tweet

Essa elegância e simplicidade, parece-me, vêm de um processo de desdobramento, desenvolvimento . Os jobs batch e corrotinas são implantados a partir de estruturas anteriores (os comandos funcionam em um shell de usuário). Acho que devido à filosofia minimalista e aos recursos limitados da equipa que criou o Unix, o sistema evoluiu lateralmente e como tal foi capaz de incorporar funcionalidades avançadas sem voltar a olhar para as mais básicas, porque não havia recursos suficientes para fazer de qualquer outra forma.

Na ausência de um processo de implantação, o alto nível será consideravelmente mais complexo do que o necessário. Ou seja, a complexidade da maioria das ferramentas declarativas se desenvolve a partir do fato de que seu nível mais alto não sai do nível baixo que estão tentando substituir.

Essa falta de desdobramento Se você omitir o neologismo, é rotineiramente justificado pela necessidade de proteger o usuário do nível inferior. Essa ênfase em usar um poka-yoke (protegendo o usuário de erros de baixo nível) tem um custo, que é uma margem de complexidade que se autodestrói porque a complexidade extra gerará novos tipos de erros. E para completar, esses tipos de erros não têm nada a ver com o problema do domínio, mas com a própria ferramenta. Não haveria exagero se descrevêssemos esses erros como um iatrogenia .

As ferramentas de modelo declarativas, quando aplicadas à tarefa de gerar visualizações HTML, são um caso arquetípico de rejeição de alto nível de nível baixo que pretende rejeitar. Como assim? Por quê gerar qualquer visão não trivial requer lógica e sistemas de modelagem, especialmente os menos lógicos, contornam a lógica completamente e tentam inserir um pouco dela quando pensam que ninguém está observando.

Nota: uma justificativa ainda pior para uma margem de complexidade é quando eles tentam vender uma ferramenta como mágico ou algo que simplesmente trabalho ; A imprecisão de baixo nível é considerada uma coisa boa porque uma ferramenta mágica sempre funciona sem que você saiba por que ou como ela funciona. Em minha experiência, quanto mais mágica a ferramenta parece, mais rápido ela transforma entusiasmo em frustração.

E quanto à separação de interesses? A visão e a lógica não deveriam permanecer separadas? O erro mais comum é presumir que a lógica de negócios e a lógica de apresentação são as mesmas. A lógica de negócios não faz sentido em um modelo, mas a lógica de apresentação existe de qualquer maneira. Ignorar a lógica de modelo coloca a lógica de apresentação de lado e a coloca no servidor, onde deve ser complicado para se adaptar. Devo toda essa explicação clara a Alexei Boronine, que apresenta um caso muito bom neste artigo .

Na minha opinião, mais ou menos dois terços do trabalho do template está em sua lógica de apresentação enquanto o outro terço é responsável por lidar com problemas genéricos como: strings de concatenação, tags fechadas, especiais de escape de caractere e a lista continua. Essa é a natureza dupla da geração de visualizações HTML. O sistema de elencos cuida do segundo tempo, mas eles não vão muito bem no primeiro tempo. Modelos sem lógica viram as costas para esses tipos de problemas sem hesitar, forçando você a consertar o problema. Outros sistemas de template sofrem porque eles realmente precisam fornecer uma linguagem de programação não trivial para que seus usuários possam escrever lógica de apresentação.

Finalmente, as ferramentas de modelo declarativo sofrem com o seguinte:

  • Se eles ousassem implantar a partir de seu problema de domínio, eles teriam que fornecer maneiras de gerar padrões lógicos;
  • Uma DSL que fornece lógica não é realmente uma DSL, mas uma linguagem de programação. Deve-se observar que outros domínios, como gerenciamento de configuração, também sofrem com a falta de 'implantação'.

Vou encerrar esta revisão com um argumento que está logicamente desconectado do tópico que traz este artigo, mas que está muito próximo de seu núcleo sentimental: Temos pouco tempo para aprender. A vida é curta e, fora disso, precisamos trabalhar. Quando encontramos nossas limitações, precisamos gastar nosso tempo aprendendo coisas novas que serão úteis e servirão ao nosso tempo, mesmo quando enfrentamos mudanças tecnológicas rápidas. É por isso que aconselho você a usar ferramentas que não apenas forneçam uma solução, mas que atendam ao domínio ao qual se aplica. RDB ensina sobre dados e Unix ensina sobre conceitos de sistema operacional, mas com ferramentas insatisfatórias que não implantam, sempre senti como se estivesse aprendendo os meandros de uma solução subótima enquanto permanecia no escuro sobre a natureza do problema a ser resolvido.

A heurística que sugiro que você considere agora são ** ferramentas de valor que iluminam seu problema de domínio, em vez de ferramentas que obscurecem seu problema de domínio por trás de recursos presunçosos **.

A abordagem dos gêmeos

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

  • Usa um idioma de estrutura de domínio de dados específico (ou dsDSL, por sua sigla em inglês linguagem específica do domínio da estrutura de dados ) para superar a separação.
  • Crie um nível alto que se desdobra do nível inferior para superar a margem de complexidade.

dsDSL

Uma estrutura de dados DSL (dsDSL) é uma DSL que é constrói com as estruturas de dados de uma linguagem de programação . A ideia central é usar estruturas de dados básicas que você tem disponíveis, como strings, números, arrays, objetos e funções, e então combinar tudo para criar extratos que possam resolver um domínio específico.

Queremos manter o controle sobre as estruturas ou ações declaradas (de alto nível) sem ter que especificar os padrões que essas construções (de baixo nível) implementam. Queremos preencher a lacuna 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 uma abordagem direta via dsDSL.

Se você tivesse perguntado há um ano, eu teria pensado que o conceito de um dsDSL era idealista, e então percebi que JSON é na verdade um ótimo exemplo dessa abordagem! Um objeto JSON analisado consiste em estruturas de dados que representam entradas de dados declarativamente para aproveitar as vantagens do DSL e também torná-lo mais fácil de analisar e manipular em uma linguagem de programação. (Pode haver outros dsDSLs no mundo, mas eu ainda não vi nenhum. Se você souber de algum, eu adoraria que você compartilhasse na seção de comentários.)

Como JSON, um dsDSL tem os seguintes atributos:

  1. Ele consiste em um pequeno conjunto de funções: JSON tem duas funções principais parse e stringify.
  2. Suas funções mais comuns têm argumentos complexos e recursivos: um JSON analisado é um array ou objeto que geralmente contém outros arrays ou objetos dentro dele.
  3. As entradas para essas funções constituem representações específicas: JSON tem um esquema de validação explícito e estritamente imposto que pode diferenciar estruturas válidas de inválidas.
  4. Tanto as entradas quanto as saídas dessas funções podem ser retidas e geradas por uma linguagem de programação sem a necessidade de uma sintaxe separada.

Mas dsDSL vai além do JSON de muitas maneiras. Vamos criar um dsDSL para gerar HTML usando Javascript. Em seguida, falarei sobre o problema de saber se essa abordagem pode ser estendida a outras linguagens (SPOILER: pode definitivamente ser feito em Ruby e Python, mas provavelmente não em C).

HTML é uma linguagem de marcação composta de etiquetas delimitado por colchetes angulares (< e >). Essas tags podem ser conteúdos e atributos opcionais. Atributos são simplesmente uma lista de atributos-chave / estimativa e o conteúdo pode ser texto ou outras tags. Atributos e conteúdos são opcionais para qualquer tag. Estou simplificando tudo isso, mas é muito bom.

Uma maneira muito direta de representar uma tag HTML em um dsDSL é usar um array com três elementos: - Tag: uma string. - Atributos: um objeto (do tipo chave / estimativa simples) ou sin definir (se nenhum atributo for necessário). - Conteúdo: uma string (texto), um array (outro rótulo) ou sin definir (se não houver conteúdo).

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

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

Para listar várias tags html no mesmo nível, poderíamos colocá-las em uma matriz:

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

O mesmo princípio pode ser aplicado para criar várias tags dentro de uma tag:

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

É claro que esse dsDSL não será muito útil para nós se não gerarmos um HTML a partir dele. Precisamos de uma função generar que fará com que nosso dsDSL crie uma string com HTML. É por isso que, se executarmos generar (['a', {href: 'views'}, 'Index']), obteremos a string Index .

A ideia de qualquer DSL é especificar algumas construções com uma certa estrutura que será então passada para uma função. Nesse caso, a estrutura que forma o dsDSL é esse array que possui de um a três elementos; essas matrizes têm uma estrutura específica. Se generar para realmente validar sua entrada (e é importante e também fácil validar totalmente a entrada, já que essas regras de validação são a analogia exata de uma sintaxe DSL), isso dirá exatamente onde você errou. Depois de um tempo, você começará a reconhecer o que torna uma estrutura válida em um dsDSL, e essa estrutura será altamente sugestiva do que é gerado subjacente a ela.

Quais são os méritos de um dsDSL em contraste com um DSL?

  • Um dsDSL é parte integrante do seu código. Ajuda a ter menos contagens de linha, contagens de arquivo e redução geral de tudo.
  • Os dsDSL são fácil para analisar (e, portanto, mais fácil de implementar e modificar). Análise é simplesmente iterar por meio de elementos de um array ou objeto. Da mesma forma, dsDSLs são relativamente fáceis de projetar porque em vez de criar uma nova sintaxe (algo que todos odiariam), você pode ficar com a sintaxe de sua linguagem de programação (algo que também é odiado, mas pelo menos você já sabe o que é )
  • Um dsDSL tem todo o controle de uma linguagem de programação. Isso significa que um dsDSL, quando usado corretamente, tem as vantagens de uma ferramenta de alto nível e também de um de baixo nível.

Agora temos o último argumento que é bastante forte, por isso vou enfatizá-lo nesta seção. O que eu quero dizer com devidamente empregado ? Para ver como isso funciona, vamos considerar um exemplo em que queremos construir uma tabela para exibir as informações de uma matriz chamada Agora, a última afirmação é forte, então vou passar o resto desta seção DATA.

var DATA = [ {id: 1, descripción: 'Producto 1', precio: 20, onSale: verdadero, categorías: ['a']}, {id: 2, descripción: 'Producto 2', precio: 60, onSale: falso, categorías: ['b']}, {id: 3, descripción: 'Producto 3', precio: 120, onSale: falso, categorías: ['a', 'c']}, {id: 4, descripción: 'Producto 4', precio: 45, onSale: verdadero, categorías: ['a', 'b']} ]

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

Também temos uma variável FILTER que, quando inicializado, será um array com as categorias que queremos exibir.

Queremos que nossa mesa faça isso:

  • Mostrar cabeçalhos de tabela.
  • Para cada produto, serão exibidos os campos: descrição, preço e categorias.
  • Não imprima o campo id mas adicione-o como um atributo id para cada linha. VERSÃO ALTERNATIVA: Adicionar um atributo id para cada elemento tr.
  • Coloque uma aula onSale se o produto está em promoção ( à venda )
  • Classifique os produtos em ordem decrescente em relação ao preço.
  • Filtre determinados produtos por categoria. Se FILTER é um array vazio, mostraremos todos os produtos. Caso contrário, mostraremos os produtos em que a categoria de produto está contida em FILTER.

Podemos criar a apresentação lógica 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]]; })]; }) ]]; }

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

Agora vamos ver o que esse código faz. Primeiro, defina uma função, drawTable para conter a apresentação lógica do desenho da tabela de produtos. Esta função recebe DATA e FILTER como parâmetros, por isso pode ser usado por diferentes conjuntos de dados e filtros. drawTable Tem dupla função, sendo parcial e ajudante.

var drawTable = function (DATA, FILTER) {

A variável interna, printableFields, é o único lugar onde você precisa especificar quais campos podem ser impressos, para evitar repetição e inconsistências quando confrontado com mudanças de requisitos.

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

Em seguida, classificamos o DATA de acordo com o preço de seus produtos. Lembre-se de que alguns critérios de classificação diferentes e mais complexos seriam mais diretos na implementação, uma vez que temos toda a linguagem de programação à nossa disposição.

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

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

queremos criar.

return ['table', [

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

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

Você pode ver que acabamos de implementar a iteração, a força de trabalho de geração de HTML, e não havia necessidade de nenhum build DLS; precisamos apenas de uma função para iterar por meio de uma estrutura de dados e retornar o dsDSL. Uma função semelhante nativa ou implementada pelo usuário teria o mesmo efeito.

Agora você deve iterar através dos produtos contidos em DATA.

dale.do (DATA, function (product) {

Verificamos se este produto foi rejeitado por FILTER. Se FILTER está vazio, podemos imprimir o produto. Se FILTER não estiver vazio, iremos iterar por categorias de produtos até encontrarmos uma que esteja dentro de 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; é adaptado às nossas necessidades e temos total liberdade para expressá-lo, pois estamos em uma linguagem de programação e não em DSL.

Se o coincidencia é falsa, um array vazio é retornado (portanto, não imprimimos este produto). Caso contrário, um

com seu respectivo id e classe, da mesma forma que iteramos em printableFields para imprimir os campos sem dúvida.

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 é divertida, certo?

})]; }) ]]; }

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

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 é o dsDSL no seu melhor . Você pode, neste ponto, parar de ler o artigo (e também deixar um comentário maldoso, porque você conquistou o direito de fazê-lo se tiver chegado a este ponto!). Mas, falando sério, se o código acima não parecer sofisticado, nada neste artigo terá.

Para aqueles que ainda estão me seguindo, gostaria de voltar à declaração de abertura desta seção, que é o que um dsDSL tem as vantagens de níveis altos e baixos :

  • o vantagem de baixo nível trata-se de escrever códigos quando queremos, livrar-nos da camisa de força DSL.

  • o vantagem de alto nível é 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 sequência com HTML).

Mas como isso é diferente de um código puramente imperativo? Acho que a elegância da abordagem dsDSL se resume ao fato de que o código escrito desta forma consiste principalmente em expressões ao invés de declarações . Para ser mais preciso, qualquer código que usa um dsDSL é praticamente composto de:

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

O código que consiste principalmente em expressões, que encapsula a maioria das declarações dentro de funções, é extremamente curto, 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.

Outra característica do dsDSL (que não pode ser muito explorada por enquanto) é a capacidade de usar tipos para aumentar a atratividade e a suculência de estruturas literais. Em um artigo futuro, enfatizarei esse ponto.

Poderia ser para criar um dsDSL sem JavaScript, a única linguagem verdadeira? Sim, acho que é 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 execução
  • Tipos de retorno dinâmico e polimorfismo

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

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

Nesta seção, tentarei mostrar uma maneira de implantar uma ferramenta de nível superior em seu domínio. Em suma, a abordagem consiste nestas etapas

  1. Pegue dois ou quatro problemas que são representações de instâncias de um problema de domínio. Esses problemas devem ser reais. Exibir o nível alto a partir do nível baixo é um problema de indução, por isso você precisa de dados reais para poder obter uma solução representativa.
  2. Resolva os problemas sem ferramenta da maneira mais rápida possível.
  3. Reserve um momento para examinar suas soluções e observe os padrões comuns que elas apresentam.
  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 capa de alto nível e verifique se as soluções estão corretas.
  7. Se você descobrir que pode representar facilmente todos os problemas com seus padrões de renderização, e os padrões de construção para cada uma dessas instâncias produzem as implementações corretas, você está pronto para prosseguir. Se não, volte para a prancheta.
  8. Se surgirem novos problemas, resolva-os com a ferramenta e corrija-os de acordo.
  9. A ferramenta deve convergir assintoticamente para um estado final, não importa quantos problemas ela esteja resolvendo. Em outras palavras, a complexidade de uma ferramenta deve permanecer constante em vez de aumentar com o número de problemas que ela resolve.

O que diabos são os padrões de renderização Y padrões de geração ? Que bom 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 a um domínio que diz respeito à sua ferramenta. É um alfabeto de estruturas que permite escrever qualquer padrão que você gostaria de expressar dentro do domínio aplicável. Em uma DSL, essas seriam as regras de produção. Agora vamos voltar ao nosso dsDSL para gerar um HTML.

A humilde tag HTML é um bom exemplo de padrões de renderização. Agora, vamos examinar mais de perto esses padrões básicos.

A humilde tag HTML é um bom exemplo de padrões de renderização. Agora, vamos examinar mais de perto esses padrões básicos. Tweet

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

  • Um rótulo: ['TAG']
  • Uma tag com atributos: ['TAG', {attribute1: value1, attribute2: value2, ...}]
  • Um rótulo com conteúdo: ['TAG', 'CONTENTS']
  • Uma tag com atributos e conteúdos: ['TAG', {attribute1: value1, ...}, 'CONTENTS']
  • Uma etiqueta com outra etiqueta dentro: ['TAG1', ['TAG2', ...]]
  • Um grupo de tags (sozinho ou dentro de outras tags): [['TAG1', ...], ['TAG2', ...]]
  • Dependendo de uma determinada condição, coloque uma etiqueta ou não: condition ? ['TAG', ...] : [] / Dependendo de uma determinada condição, coloque um atributo ou não: ['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 renderizar qualquer HTML que precisar. Padrões mais sofisticados, como uma iteração condicional por meio de um objeto para gerar uma tabela, podem ser implementados com funções que retornam os padrões de renderização mencionados acima e esses padrões mapeiam diretamente para tags HTML.

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

  • Valide a entrada (na verdade, este é um padrão de geração universal).
  • Abrir e fechar tags (mas tags vazias como, que fecham sozinhas)
  • Define atributos e conteúdos, escapando de caracteres especiais (mas não o conteúdo de rótulos e)

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

E, finalmente, o que quero dizer com ' ande então deslize ? Quando nos deparamos com um problema de domínio, queremos usar uma ferramenta que nos poupe dos terríveis detalhes desse domínio. Em outras palavras, queremos nos livrar do nível baixo sem que ninguém perceba e, quanto mais rápido, melhor. A abordagem “ande e depois deslize” propõe exatamente o oposto disso: ficar mais tempo no nível mais baixo. Faça amizade com suas peculiaridades e entenda quais são essenciais e quais podem ser evitadas quando surgirem problemas reais, variados e úteis.

Depois de caminhar por algum tempo no nível inferior e então resolver problemas úteis, você terá uma compreensão mais profunda de seu domínio. Os padrões de representação e geração surgirão naturalmente; Elas derivam da natureza dos problemas que estão tentando resolver. Então você pode escrever o código que os usará. Se funcionarem, você será capaz de superar os problemas que teve que enfrentar recentemente. Planar significa muitas coisas; implica velocidade, precisão e falta de atrito. Talvez essa qualidade seja sentida mais quando você está resolvendo problemas com esta ferramenta, você sente que está entrando no problema ou está escapando dele?

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

Uma ferramenta de implantação não só resolverá um problema, mas também o ajudará a entender a estrutura do problema.

Portanto, não fuja de um problema valioso. Passe por ele primeiro e depois deslize por ele.

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

Apresentando Battlescripts: Bots, Ships, Mayhem!

Web Front-End

Apresentando Battlescripts: Bots, Ships, Mayhem!
O impacto do Brexit no setor de serviços financeiros

O impacto do Brexit no setor de serviços financeiros

Processos Financeiros

Publicações Populares
Melhores práticas de fusões e aquisições na América Latina
Melhores práticas de fusões e aquisições na América Latina
As 25 melhores predefinições do Lightroom Mobile para fotos impressionantes do iPhone
As 25 melhores predefinições do Lightroom Mobile para fotos impressionantes do iPhone
EUA: corrida para prefeito de Honolulu segue para segundo turno
EUA: corrida para prefeito de Honolulu segue para segundo turno
Como fazer autenticação JWT com um Angular 6 SPA
Como fazer autenticação JWT com um Angular 6 SPA
Como editar fotos no iPhone com iOS Photos
Como editar fotos no iPhone com iOS Photos
 
React Test-driven Development: From User Stories to Production
React Test-driven Development: From User Stories to Production
Criação de uma API REST segura em Node.js
Criação de uma API REST segura em Node.js
Um guia de campo para DesignOps
Um guia de campo para DesignOps
Donald Trump encontra o Papa Francisco no Vaticano, jura não esquecer sua mensagem
Donald Trump encontra o Papa Francisco no Vaticano, jura não esquecer sua mensagem
Automação no Selenium: Modelo de objeto de página e fábrica de página
Automação no Selenium: Modelo de objeto de página e fábrica de página
Categorias
NutriçãoVida DesignerGestão De EngenhariaPessoas E Equipes De ProdutoFamíliaCiclo De Vida Do ProdutoAprendendoFerramentas E TutoriaisKpis E AnálisesFuturo Do Trabalho

© 2023 | Todos Os Direitos Reservados

socialgekon.com