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 .
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:
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?
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.
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.
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.
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:
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.
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:
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.
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 deaplicació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.
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:
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 **.
Para superar os dois problemas de programação declarativa que mencionei aqui, proponho uma abordagem dupla:
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:
parse
e stringify
.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?
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:
id
mas adicione-o como um atributo id
para cada linha. VERSÃO ALTERNATIVA: Adicionar um atributo id
para cada elemento tr
.onSale
se o produto está em promoção ( à venda )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 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 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 Verificamos se este produto foi rejeitado por 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 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 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: 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: 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. 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 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. Os padrões de renderização para HTML são os seguintes: 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: 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. queremos criar.
return ['table', [
printableFields
e gerar cabeçalhos de tabela para cada um deles: ['tr', dale.do (printableFields, function (field) { return ['th', field]; })],
DATA
. dale.do (DATA, function (product) {
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; });
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]];
})]; }) ]]; }
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 () ]); }
Ande primeiro, depois deslize: como revelar o alto a partir do baixo
['TAG']
['TAG', {attribute1: value1, attribute2: value2, ...}]
['TAG', 'CONTENTS']
['TAG', {attribute1: value1, ...}, 'CONTENTS']
['TAG1', ['TAG2', ...]]
[['TAG1', ...], ['TAG2', ...]]
condition ? ['TAG', ...] : []
/ Dependendo de uma determinada condição, coloque um atributo ou não: ['TAG', {class: condition ? 'someClass': undefined}, ...]