Esta é uma continuação do artigo
“Por que raquete? Por que Lisp? Escrevi cerca de um ano depois de descobrir o
Racket . Como um novato, eu não conseguia entender os elogios que derramavam sobre Lisp de todos os lados. Eu não sabia o que pensar. Como entender que o Lisp acabará por causar
"iluminação profunda" . Ok, mano, você diz.
Eu tinha uma pergunta simples: de
que serve? Em um artigo anterior, tentei responder e resumi as razões pelas quais alguém iria querer aprender Lisp ou, em particular, Racket.
Eu compilei uma lista dos
nove recursos de idiomas mais valiosos para mim como novato no Racket. Por exemplo, o recurso número 5 é "a criação de novas linguagens de programação". Esse método também é chamado
de programação orientada a linguagem , ou
JOP .
Desde então, a IOP se tornou minha parte favorita da Racket, e compartilhei minha admiração no livro online
Beautiful Racket , que explica a técnica da ROP e a ferramenta Racket.
Um exemplo no meu trabalho é o
pólen . Eu escrevi essa linguagem de programação para uma tipografia conveniente dos meus livros on-line. No pólen, o parágrafo anterior está programado da seguinte forma:
#lang pollen ◊link["https://beautifulracket.com/appendix/why-racket-why-lisp.html#so-really-whats-in-it-for-me-now"]{ }, Racket. , № 5 — « ». ◊em{- }, ◊em{}.
Outro exemplo é o
brag , um gerador de analisador (no estilo de
lex/yacc
), que usa a
gramática BNF como código-fonte. Um exemplo simples para a linguagem
bf :
#lang brag bf-program : (bf-op | bf-loop)* bf-op : ">" | "<" | "+" | "-" | "." | "," bf-loop : "[" (bf-op | bf-loop)* "]"
Os dois idiomas são implementados no Racket e podem ser executados com o intérprete comum do Racket ou dentro do Racket IDE (chamado DrRacket).
Questões principais
E, no entanto ... Apesar do fato de o livro ter forçado milhares de pessoas a começar a explorar Racket, às vezes me parece que estou pisando no mesmo terreno que os fãs de Lisp que uma vez eu critiquei.
Se o NOP é tão legal, por que passar alguns dias lendo um livro? Certo? Eu posso explicar tudo brevemente, sem mais delongas. Duas perguntas simples precisam ser respondidas:
- Quais problemas são mais adequados para a programação de idiomas?
- Por que o Racket é melhor para criar idiomas?
A segunda pergunta é simples. O primeiro não é. Me perguntaram muitas vezes. Muitas vezes citei a
famosa frase do juiz Potter Stewart: você a entenderá quando a vir. A resposta é boa o suficiente para aqueles que estão realmente interessados. Mas não para aqueles que estão do lado e gostariam de ouvir argumentos substantivos.
Então, eu vou tentar. Tenha em mente que eu não sou professor de ciência da computação e não posso falar sobre a teoria das linguagens de programação. Em vez disso, uso Racket e linguagens específicas de domínio (DSL) para fins práticos: meu trabalho diário depende deles. Portanto, vou me concentrar em aspectos práticos.
Resposta curta
- JOP é realmente um método de design de interface. É ideal para tarefas que exigem notação mínima , mantendo a precisão máxima . Notação mínima significa a única notação permitida. Nada mais. Precisão máxima, ou seja, o significado dessa notação, é exatamente o que você diz. Sem ambiguidade ou padrões. A PIO chega ao ponto como nada mais.
(O impaciente pode passar para categorias específicas de tarefas que serão beneficiadas pelo NOP).
- A raquete é ideal para OOP devido ao seu sistema macro . Eles funcionam no estilo de compilador, simplificando a conversão de código. O sistema de macro Racket é melhor que qualquer outro.
Nesse ponto, metade dos leitores do artigo desejará publicar comentários anônimos criticando minhas teses. Mas lembre-se: de qualquer forma, eu venci. JOP e Racket aumentaram incrivelmente minha produtividade de programação. É um prazer compartilhar esse conhecimento para que você também possa aproveitar esses benefícios. Mas também ficarei feliz se essas ferramentas continuarem sendo minha arma secreta. Nesse caso, ficarei em 0,01% dos programadores mais produtivos, obtendo um resultado mais impressionante e lucrativo do que os 99,9% restantes
Então a escolha é sua.
Resposta longa
Se você pensa nas questões mais importantes, elas se resumem a uma meta-pergunta: por que é difícil explicar os benefícios das armas nucleares?
Talvez quando falamos de
idiomas , o termo esteja carregado de expectativas sobre o que é e o que é uma língua. Enquanto estamos dentro desse paradigma, é difícil entender o valor das linguagens de programação.
Mas se você reduzir a escala e considerar os idiomas como parte de uma categoria mais ampla de interfaces homem-computador, será mais fácil ver as vantagens específicas do OOP. Então vamos lá.
Idiomas de uso geral e idiomas orientados ao assunto
Em primeiro lugar, um pouco de terminologia. A programação orientada a linguagem (também conhecida como OOP) é a idéia de resolver problemas de programação criando uma nova linguagem e depois escrevendo um programa nela. Freqüentemente, esses "idiomas pequenos" são chamados de DSLs (idiomas específicos do domínio).
Como o nome indica, uma linguagem orientada ao assunto é adaptada às tarefas de um determinado campo. Por exemplo, PostScript, SQL,
make
, expressões regulares,
.htaccess
e HTML são considerados linguagens orientadas a assuntos. Eles não estão tentando fazer tudo. Em vez disso, eles estão focados em fazer uma coisa bem.
No outro extremo do espectro
estão as linguagens de uso geral . Aqui vemos C, Pascal, Perl, Java, Python, Ruby, Racket, etc. Por que eles não são considerados orientados ao assunto? Porque eles se posicionam para uma ampla gama de tarefas de computação.
Na prática, as linguagens de uso geral geralmente se especializam em um campo específico. Por exemplo, C é melhor que outros para a programação do sistema. Perl - para scripts na administração do sistema. Python se destaca como uma linguagem para iniciantes. Raquete para programação orientada a linguagem. Em cada caso, é para isso que o idioma foi originalmente projetado.
Há uma linha tênue entre DSL e linguagens de uso geral. Por exemplo, o Ruby foi criado como uma linguagem de uso geral, mas tornou-se popular principalmente para aplicativos da web por meio de sua associação ao Ruby on Rails. O JavaScript, por outro lado, era originalmente uma linguagem orientada ao assunto para scripts de navegador da web. Mas ele sofreu uma mutação como um vírus e, desde então, cresceu muito além da tarefa original.
O que é um idioma?
Se todo esse amplo espectro é chamado de linguagem, quais são as características definidoras da linguagem?
Eu sei o que você está pensando: “É aqui que você está enganado. HTML não é uma linguagem. Isso é apenas marcação. Ele não pode descrever o algoritmo. Ou: “Expressões regulares não são um idioma. Eles não trabalham por conta própria. É apenas sintaxe para outro idioma. "
Eu também pensava assim. Mas quanto mais perto eu olhava, mais nebulosas essas diferenças pareciam. Assim, minha primeira afirmação principal (de três): a linguagem de programação é inerentemente um meio de troca -
um sistema de notação que é compreensível para pessoas e computadores .
O sistema de notação (notação) significa que o idioma possui sintaxe. “Limpar” significa que, com sua sintaxe, o idioma transmite
significado (ou
semântica , se você usar uma palavra mais sofisticada). Esta definição abrange todas as linguagens de programação de uso geral. E tudo DSL. (Mas nem todos os fluxos de dados, que serão discutidos em mais detalhes posteriormente).
(A propósito, embora “programação” e “linguagem” sejam palavras usadas em conjunto, essas linguagens não são usadas apenas por pessoas para programar computadores. Às vezes, são usadas por computadores para se comunicar conosco (por exemplo, expressões S), às vezes para se comunicar umas com as outras. (por exemplo, XML, JSON, HTML.) Definitivamente, parece errado excluir esses recursos. Mas, na prática, sim - o que geralmente fazemos com uma linguagem de programação é, de fato, programação).
Considere o código HTML: uma maneira de dizer ao computador - em particular um navegador da web - como desenhar uma página da web. Este é um sistema de notação (colchetes angulares, tags, atributos etc.) compreensível para o homem e o computador (o atributo
charset
indica a codificação de caracteres, a tag
p
contém um parágrafo etc.).
Aqui está uma pequena página HTML:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>My web page</title> </head> <body> <p>Hello <strong>world</strong></p> </body> <html>
Suponha que você não concorda que o HTML é uma linguagem de programação. Bom Vamos exibir nossa página em Python. Esta é uma linguagem de programação real, certo?
print "<!DOCTYPE html>" print "<html>" print "<head>" print "<meta charset=\"UTF-8\">" print "<title>My web page</title>" print "</head>" print "<body>" print "<p>Hello <strong>world</strong></p>" print "</body>" print "<html>"
Se o Python é uma linguagem de programação, mas o HTML não é, então esta amostra do Python é um programa, mas a amostra do HTML não é.
Obviamente, essa é uma diferença torturada. Aqui, a pythonização não adiciona nada além de complexidade e estereotipagem. O importante é que o único conteúdo semântico interessante em um programa Python - do ponto de vista do gerenciamento de um navegador da Web - é que ele está incorporado em HTML (talvez tags HTML como
DOCTYPE
,
meta
e
strong
possam ser consideradas funções que levam argumentos). A lógica nos leva a concluir que o HTML, embora mais simples e menos flexível, ainda é uma linguagem de programação.
Linguagens incorporadas
Um exemplo com HTML e Python que criamos. Mas incorporar DSL em outro idioma é onipresente. Os idiomas usados dessa maneira são chamados de
incorporados . Eles representam a forma mais comum de programação de linguagem. Como programador, você confia no JOP há muitos anos, mesmo que não saiba o nome dele.
Por exemplo, expressões regulares (outros exemplos:
printf
para formatar seqüências de caracteres, CLDR para padrões de data / hora, SQL). Não podemos pensar na expressão regular como uma linguagem independente. Mas todo programador sabe o que é:
^fo+(bar)*$
Além disso, você provavelmente pode inserir essa expressão regular na sua linguagem de programação favorita e ela simplesmente funcionará. Esse comportamento consistente só é possível porque a notação de expressão regular é uma linguagem externa (
POSIX ).
Assim como no HTML, podemos escrever uma expressão equivalente na notação da linguagem host. Por exemplo, o Racket suporta
expressões regulares do esquema (SRE): são expressões regulares com notação de
expressão S. O modelo acima será escrito assim:
(seq bos "f" (+ "o") (* (submatch "bar")) eos)
Mas os programadores de raquete raramente usam expressões SRE. Eles são muito longos e difíceis de lembrar.
Outro exemplo onipresente de DSL incorporado: expressões matemáticas. Todo programador sabe o que isso significa:
(1 + 2) * (3 / 4) - 5
Expressões matemáticas sozinhas não criam programas interessantes. Precisamos combiná-los com outras construções de linguagem. Mas, como nas expressões regulares, esse é um registro ergonômico e prático. As expressões matemáticas têm sua própria notação e significados que são compreensíveis para pessoas e computadores; portanto, eles se qualificam como uma linguagem interna separada.
Você está brincando que o HTML está programando?
Não é Afirmo que o HTML (expressões regulares e expressões matemáticas) se qualifica como linguagens de programação rudimentares. Isso significa que escrever HTML (ou expressões regulares ou expressões matemáticas) se qualifica como programação rudimentar.
Por favor, não entre em pânico. Obviamente, um "programador" no LinkedIn com conhecimento apenas de HTML e aritmética, isso é um absurdo (embora em uma semana ele provavelmente consiga um emprego por US $ 180 mil). Mas essa é uma questão à parte, o que “programador” significa no mercado de trabalho. Nós não estamos falando sobre isso.
Armadilha da Totalidade de Turing
Se essa definição de linguagens de programação ainda o incomoda, talvez você pense que uma linguagem de programação real deve expressar todos os algoritmos possíveis - ou seja, deve ser
Turing completo .
Entendo que intuitivamente esse pensamento se sugere. Cada linguagem de programação de uso geral é Turing completa.
Mas o problema é que esta é uma barra baixa. A perfeição de Turing é uma métrica técnica que não corresponde ao uso da linguagem no mundo real. Por exemplo, expressões regulares não são completas de Turing, mas são úteis ao expressar muitos cálculos com anotações mínimas. O HTML também não é Turing completo, mas é uma maneira útil de controlar o navegador. Pelo contrário, a linguagem
bf é Turing completa, mas mesmo as tarefas mais triviais exigem quilômetros de código intransitável.
Restrições de idioma
Existe alguma coisa sob minha definição de linguagem? Não.
- Formatos de dados binários não são considerados idiomas. Por exemplo, um arquivo
jpeg
. Embora um computador possa entendê-los, uma pessoa não. Ou PDF: se você o hackear, há algumas partes que podem ser lidas por humanos. Mas isso se deve à maneira como o PDF funciona. Não faz sentido escrever idéias usando construções em PDF.
- Arquivos de texto não são idiomas. Suponha que tenhamos um arquivo com a Ilíada de Homero. Nós, humanos, podemos ler e entender. Embora o computador possa processar trivialmente o arquivo, digamos, imprimindo seu conteúdo, o texto é incompreensível para o computador.
- As interfaces gráficas do usuário não são idiomas. Sim, esses são sistemas de notação (que dependem de texto e imagem). Mas eles são compreensíveis apenas para as pessoas. Os computadores desenham uma GUI, mas não os entendem.
Idiomas como interfaces
Acima, descrevi a linguagem de programação como um "meio de troca" entre pessoas e computadores. Assim, as línguas se encaixam em uma categoria mais ampla, que chamamos de
interfaces .
Isso leva à segunda declaração básica (de três): que a
programação da linguagem é basicamente um método de projetar uma interface . Se você gosta de pensar em interfaces, vai gostar do JOP. Caso contrário, você ainda gostará do JOP por tornar possíveis interfaces que, de outra forma, seriam inatingíveis.
Um dos meus exemplos favoritos de um idioma como interface é o
gabar , um idioma gerador de analisador criado com o Racket. Se você já usou a cadeia de ferramentas lex / yacc, sabe que frequentemente o objetivo é gerar um analisador a partir de uma
gramática BNF . Por exemplo, para
bf, fica assim:
bf-program : (bf-op | bf-loop)* bf-op : ">" | "<" | "+" | "-" | "." | "," bf-loop : "[" (bf-op | bf-loop)* "]"
Para criar um analisador em um idioma de uso geral, você precisa traduzir essa gramática em um monte de código nativo. Este é um trabalho tedioso. E sem sentido - já não escrevemos a gramática? Por que fazer de novo?
No entanto, o
brag
realiza o nosso desejo. Para fazer o analisador, basta adicionar a linha
#Lang brag
ao arquivo, que converte magicamente a gramática BNF para o código-fonte
brag
:
#Lang brag bf- : (Bf-op | Bf-loop)* bf-op : ">" | "<" | "+" | "-" | "."| "," Bf-loop : "["(Bf-op | Bf-loop)* "]"
Feito! Na compilação, esse arquivo exporta a função de
parse
, que implementa essa gramática BNF.
Este é um dos meus exemplos favoritos, porque é inegavelmente superior a outras opções. Além disso, com uma linguagem de uso geral, essa interface é praticamente impossível.
Mas um programador JOP está constantemente criando essas interfaces.
Onde a linguagem é a melhor interface
Isso me leva à minha terceira e última tese básica de que as
linguagens têm vantagens únicas sobre as interfaces . Obviamente, as categorias abaixo não são exaustivas ou exclusivas. Mas descobri que a PIO tem muito a oferecer em tais situações:
1. Quando você deseja criar uma interface para programadores menos qualificados, não programadores ou programadores preguiçosos (não subestime o tamanho da última categoria).
Por exemplo, o Racket possui uma sofisticada
biblioteca de aplicativos da web . Mas um servidor web simples também pode ser iniciado rapidamente usando o idioma do
web-server/insta
:
#lang web-server/insta (define (start request) (response/xexpr '(html (body "Hello LOP World"))))
Matthew Flatt em seu artigo
"Criando idiomas na raquete" demonstra o idioma que gera aventuras de texto jogáveis. Como o
brag
, parece mais uma especificação do que um programa, mas funciona:
#lang txtadv ===VERBS=== north, n "go north" south, s "go south" get _, grab _, take _ "get" ===THINGS=== ---cactus--- get "Ouch!" ===PLACES=== ---desert--- "You're in a desert. There is nothing for miles around." [cactus, key] north meadow south desert
2. Quando você deseja simplificar a notação. Um exemplo são expressões regulares. Outro exemplo é o meu
pólen de linguagem orientada ao assunto para escrever livros on-line. O pólen é semelhante ao Racket, somente aqui você começa a trabalhar no modo de texto e usa caracteres especiais para indicar os comandos do Racket incorporados ao conteúdo (o pólen se baseia na linguagem de documentação do Racket chamada
Scribble , que ocupa a maior parte da carga). Portanto, o início deste parágrafo está programado da seguinte maneira:
. — . — - ◊link["https://pollenpub.com/"]{Pollen} -.
O pólen se preocupa em colar todas as tags necessárias e convertê-las em HTML infalível. Ainda tenho todas as vantagens da marcação manual (controle total sobre a página), mas não há desvantagens (por exemplo, não posso deixar acidentalmente uma tag não fechada).
Outro exemplo de notação simplificada é
lindenmayer
, o
sistema Lindenmayer de geração de fractal e linguagem de desenho, como este:
Em uma raquete típica, um programa Lindenmayer pode ser assim:
#lang racket/base (require lindenmayer/simple/compile) (define (finish val) (newline)) (define (A value) (display 'A)) (define (B value) (display 'B)) (lindenmayer-system (void) finish 3 (A) (A -> AB) (B -> A))
Mas você pode usar a notação simplificada simplesmente alterando a notação
#lang
na parte superior do arquivo:
#lang lindenmayer/simple ## axiom ## A ## rules ## A -> AB B -> A ## variables ## n=3
O idioma supõe que você já esteja familiarizado com o sistema L. Mas uma notação simplificada facilita a anotação de seus desejos em um programa que faz o que você deseja.
3. Quando você deseja trabalhar com uma notação existente. Vimos acima como o
brag
usa a gramática BNF como código-fonte.
#lang brag bf-program : (bf-op | bf-loop)* bf-op : ">" | "<" | "+" | "-" | "." | "," bf-loop : "[" (bf-op | bf-loop)* "]"
Outro exemplo As pessoas que experimentaram o pólen disseram: "Sim, isso é ótimo, mas eu prefiro o Markdown". Não há problema:
pollen/markdown
é um dialeto de pólen que oferece semântica para pólen, mas aceita a notação usual de
pollen/markdown
:
. — . — - [Pollen]("https://pollenpub.com/") -.
A coisa mais agradável? Escrevi esse dialeto em apenas uma hora, combinando o
analisador Markdown com o código existente.
4. Se você deseja criar um destino intermediário para outros idiomas. JSON, YAML, expressões S e XML são todas linguagens orientadas a assuntos que definem formatos de dados para leitura e gravação de máquina.
No Perfect Racket, um idioma de treinamento é chamado
jsonic
. Ele permite incorporar expressões de raquete no JSON, tornando o JSON programável. O código fonte fica assim:
#lang jsonic // a line comment [ @$ 'null $@, @$ (* 6 7) $@, @$ (= 2 (+ 1 1)) $@, @$ (list "array" "of" "strings") $@, @$ (hash 'key-1 'null 'key-2 (even? 3) 'key-3 (hash 'subkey 21)) $@ ]
Compila para JSON regular:
[ null, 42, true, ["array","of","strings"], {"key-1":null,"key-3":{"subkey":21},"key-2":false} ]
5. Quando a parte principal do programa é configuração. Por exemplo, Dotfiles pode ser descrito como DSL. Um exemplo mais complexo do Racket é o Riposte de Jesse Alama, uma linguagem para testar a API HTTP baseada em JSON:
#lang riposte $productId := 41966 $qty := 5 $campaignId := 1 $payload := { "product_id": $productId, "campaign_id": $campaignId, "qty": $qty } POST $payload cart/{uuid}/items responds with 200 $itemId := /items/0/cart_item_id GET cart responds with 200
Como uma linguagem de script em miniatura, o Riposte é muito mais inteligente que o arquivo de pontos médio. Ele oculta todo o código intermediário necessário para transações HTTP e permite que o usuário se concentre em escrever testes. Ainda é a limpeza da casa. Mas pelo menos você pode se concentrar na família de que gosta.
Por que raquete?
Frequentemente, os críticos da JOP perguntam: “Por que criar uma linguagem orientada ao assunto? É mais fácil escrever uma biblioteca nativa? ”
Não, não é mais fácil se você tiver a ferramenta certa. A raquete é incomum: foi projetada desde o início especificamente para o YOP.
Portanto, implementar o DSL no Racket é mais rápido, mais barato e mais fácil do que as alternativas. Por exemplo, na primeira lição do meu livro, mostrei como desenvolver um novo idioma em uma hora - mesmo que você nunca tenha usado o Racket.Sob o capô de todas as DSLs do Racket, o compilador fonte a fonte realmente funciona , o que converte a notação e a semântica DSL em um programa Racket equivalente. Por esse motivo, o Racket DSL não poderá executar tão rápido quanto o código C. escrito manualmente. Porém, todas as ferramentas e bibliotecas do Racket estão disponíveis para cada DSL. Você perde em produtividade, mas ganha repetidamente em conveniência. E quando a criação de uma DSL é conveniente e fácil, ela se torna uma opção realista para uma gama muito maior de problemas.Assim, para responder às críticas - não, o DSL não requer necessariamente mais trabalho do que a biblioteca nativa. Além disso, como já vimos, como interface, uma linguagem pode fazer o que uma biblioteca nativa não pode.Por que macro?
Como todas as DSLs são compiladas no Racket, o programador deve escrever alguns transformadores de sintaxe que convertem a notação DSL no Racket nativo. Esses conversores de sintaxe são conhecidos como macros . De fato, eles podem ser descritos como extensões para o compilador Racket.O sistema macro de raquete é vasto, elegante e é sem dúvida a pérola de sua coroa. Grande parte do meu livro é sobre o prazer de trabalhar com macros Racket. Eu posso citar duas funções pendentes:- Racket , . . , , Racket , , , , , . ( . « » ).
- As macros de raquete são higiênicas , ou seja, por padrão, o código criado pela macro preserva o contexto lexical do qual a macro é definida. Na prática, isso elimina a enorme quantidade de gestos desnecessários que geralmente são necessários para o DSL (para obter mais detalhes, consulte o capítulo "Higiene" ).
É possível implementar DSL em, digamos, Python? Claro.
De fato, escrevi meu primeiro DSL especificamente em Python - e ainda o uso em meu trabalho de criação de tipos . Bem, isso. Uma vez foi o suficiente. Desde então, tenho usado o Racket.Conclusão: vitória com o YaOP
No momento, você pode ter uma de duas reações:- « , , » . , . , , . . . , . .
- «, , c Racket » . , - Riposte , — ( ):
[ ] - Racket. , , - … : « API , ?» : « Riposte». , , [DSL], , . «» Racket. DSL , .
No final do artigo “Por que raquete? Por que Lisp? Eu disse que a linguagem Lisp "lhe dá a oportunidade de liberar seu potencial como programador e pensador e, assim, aumentar suas expectativas sobre o que você pode alcançar".A PIO oferece uma oportunidade semelhante: aumentar nossas expectativas em relação ao que as linguagens de programação podem fazer por nós. Idiomas não são caixas pretas. Essas são as interfaces que podemos projetar. Ao mesmo tempo, abrimos novas possibilidades para o que pode ser feito com a ajuda de programas.Se você puder encontrar a melhor técnica de programação, use-a. Agora que tenho OOP e Racket, nunca mais voltarei.Leitura adicional