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. TweetAntes 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:
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?
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 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.
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:
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.
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:
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 debatch 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:
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 .
Para superar os dois problemas de programação declarativa, que apresentei aqui, proponho uma abordagem dupla:
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:
parse
e stringify
.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?
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:
id
campo, mas adicione-o como um id
atributo para cada linha. VERSÃO ALTERNATIVA: Adicionar um id
atributo para cada tr
elemento.onSale
se o produto está à venda.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 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 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 Verificamos se este produto foi omitido por 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 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 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 : 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: 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: 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. 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 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. Os padrões de representaçã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 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: 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.table
como seu primeiro elemento e seu conteúdo como o segundo. Esta é a representação dsDSL de 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, 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; });
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]];
})]; }) ]]; }
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 () ]); }
Ande e 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 condição, coloque um atributo ou nenhum atributo: ['TAG', {class: condition ? 'someClass': undefined}, ...]