Eu tenho apenas um conhecimento limitado do Lisp (tentando aprender um pouco no meu tempo livre), mas até onde eu entendo, as macros do Lisp permitem introduzir novas construções de linguagem e sintaxe, descrevendo-as no próprio Lisp. Isso significa que uma nova construção pode ser adicionada como uma biblioteca, sem alterar o compilador / interpretador Lisp.
Essa abordagem é muito diferente da de outras linguagens de programação. Por exemplo, se eu quisesse estender o Pascal com um novo tipo de loop ou algum idioma específico, teria que estender a sintaxe e a semântica da linguagem e, em seguida, implementar esse novo recurso no compilador.
Existem outras linguagens de programação fora da família Lisp (ou seja, além de Common Lisp, Scheme, Clojure (?), Racket (?), Etc) que oferecem uma possibilidade semelhante de estender a linguagem dentro da própria linguagem?
EDITAR
Por favor, evite discussões prolongadas e seja específico em suas respostas. Em vez de uma longa lista de linguagens de programação que podem ser estendidas de uma maneira ou de outra, gostaria de entender, de um ponto de vista conceitual, o que é específico das macros Lisp como um mecanismo de extensão e quais linguagens de programação não-Lisp oferecem algum conceito isso é perto deles.
fonte
Respostas:
O Scala também torna isso possível (na verdade, ele foi projetado conscientemente para suportar a definição de novas construções de linguagem e até DSLs completas).
Além das funções de ordem superior, lambdas e currying, que são comuns em linguagens funcionais, existem alguns recursos especiais de linguagem aqui para ativar isso *:
a.and(b)
é equivalente a uma and b
infixopara chamadas de função de parâmetro único, você pode usar colchetes em vez de chaves normais - isso (junto com o curry) permite escrever coisas como
onde
withPrintWriter
é um método simples com duas listas de parâmetros, ambas contendo um único parâmetromyAssert(() => x > 3)
em um formato mais curto, comomyAssert(x > 3)
A criação de um exemplo de DSL é discutida em detalhes no Capítulo 11. Idiomas específicos de domínio no Scala do livro gratuito Programming Scala .
* Não quero dizer que sejam exclusivos do Scala, mas pelo menos não parecem ser muito comuns. Eu não sou especialista em linguagens funcionais.
fonte
O Perl permite o pré-processamento de seu idioma. Embora isso não seja frequentemente usado para alterar radicalmente a sintaxe no idioma, ele pode ser visto em alguns dos ... módulos ímpares:
Também existe um módulo que permite ao perl executar código que parece ter sido escrito em Python.
Uma abordagem mais moderna para isso no perl seria usar Filter :: Simple (um dos módulos principais do perl5).
Observe que todos esses exemplos envolvem Damian Conway, conhecido como "Mad Doctor of Perl". Ainda é uma capacidade incrivelmente poderosa dentro do perl de distorcer a linguagem da maneira que se deseja.
Mais documentação para esta e outras alternativas existe no perlfilter .
fonte
Haskell
Haskell tem "Template Haskell" e "Quasiquotation":
http://www.haskell.org/haskellwiki/Template_Haskell
http://www.haskell.org/haskellwiki/Quasiquotation
Esses recursos permitem que os usuários aumentem drasticamente a sintaxe do idioma fora dos meios normais. Eles também são resolvidos no momento da compilação, o que eu acho que é uma grande obrigação (pelo menos para as linguagens compiladas) [1].
Eu usei a quasiquotação no Haskell uma vez antes para criar um comparador de padrões avançado em uma linguagem do tipo C:
[1] Caso contrário, o seguinte se qualifica como extensão de sintaxe:, o
runFeature "some complicated grammar enclosed in a string to be evaluated at runtime"
que obviamente é um monte de porcaria.fonte
forM
).Tcl tem um longo histórico de suporte à sintaxe extensível. Por exemplo, aqui está a implementação de um loop que itera três variáveis (até parar) sobre os cardeais, seus quadrados e cubos:
Isso seria usado assim:
Esse tipo de técnica é amplamente utilizada na programação Tcl, e a chave para fazê-lo de forma sadia é os comandos
upvar
euplevel
(upvar
liga uma variável nomeada em outro escopo a uma variável local euplevel
executa um script em outro escopo: nos dois casos,1
indica que o escopo em questão é do chamador). Também é usado muito em código que combina com bancos de dados (executando algum código para cada linha em um conjunto de resultados), em Tk para GUIs (para vincular retornos de chamada a eventos), etc.No entanto, isso é apenas uma fração do que é feito. A linguagem incorporada nem precisa ser Tcl; pode ser praticamente qualquer coisa (contanto que equilibre seus aparelhos - as coisas ficam sintaticamente horríveis, se isso não for verdade - que é a enorme maioria dos programas) e o Tcl pode apenas enviar para a língua estrangeira incorporada, conforme necessário. Exemplos disso incluem incorporar C para implementar comandos Tcl e o equivalente ao Fortran. (Indiscutivelmente, todos os comandos internos do Tcl são feitos dessa maneira em certo sentido, na medida em que são realmente apenas uma biblioteca padrão e não a própria linguagem.)
fonte
Isso é parcialmente uma questão de semântica. A idéia básica do Lisp é que o programa seja um dado que possa ser manipulado. Os idiomas comumente usados na família Lisp, como o Scheme, não permitem adicionar novos sintaxe no sentido do analisador; são apenas listas entre parênteses, delimitadas por espaço. Só que, como a sintaxe principal faz tão pouco, é possível criar praticamente qualquer construção semântica . Scala (discutido abaixo) é semelhante: as regras de nome de variável são tão liberais que você pode facilmente criar DSLs agradáveis (mantendo-se dentro das mesmas regras de sintaxe principal).
Essas linguagens, embora não permitam definir uma nova sintaxe no sentido de filtros Perl, têm um núcleo suficientemente flexível que você pode usá-lo para criar DSLs e adicionar construções de linguagem.
O recurso comum importante é que eles permitem definir construções de linguagem que funcionam tão bem quanto as incorporadas, usando recursos expostos pelas linguagens. O grau de suporte para esse recurso varia:
sin()
,round()
, etc., sem qualquer forma de implementar o seu próprio.static_cast<target_type>(input)
,dynamic_cast<>()
,const_cast<>()
,reinterpret_cast<>()
) pode ser emulado usando as funções de modelo, que usa impulso paralexical_cast<>()
,polymorphic_cast<>()
,any_cast<>()
, ....for(;;){}
,while(){}
,if(){}else{}
,do{}while()
,synchronized(){}
,strictfp{}
) e não permitem definir o seu próprio. Em vez disso, Scala define uma sintaxe abstrata que permite chamar funções usando uma sintaxe conveniente da estrutura de controle, e as bibliotecas usam isso para definir efetivamente novas estruturas de controle (por exemplo,react{}
na biblioteca de atores).Além disso, você pode examinar a funcionalidade de sintaxe personalizada do Mathematica no pacote Notação . (Tecnicamente, ele está na família Lisp, mas possui alguns recursos de extensibilidade feitos de maneira diferente, bem como a extensibilidade usual do Lisp.)
fonte
(defmacro ...)
. Atualmente, estou portando esse idioma para o Racket, apenas por diversão. Mas, eu concordo que é algo não muito útil, uma vez que a sintaxe das expressões S é mais que suficiente para a maioria dos possíveis s semânticos úteis.(define-macro ...)
equivalente que, por sua vez, pode usar qualquer tipo de análise internamente.Rebol soa quase como o que você está descrevendo, mas um pouco de lado.
Em vez de definir sintaxe específica, tudo no Rebol é uma chamada de função - não há palavras-chave. (Sim, você pode redefinir
if
ewhile
se realmente desejar). Por exemplo, esta é umaif
declaração:if
é uma função que recebe 2 argumentos: uma condição e um bloco. Se a condição for verdadeira, o bloco é avaliado. Parece a maioria das línguas, certo? Bem, o bloco é uma estrutura de dados, não está restrito ao código - este é um bloco de blocos, por exemplo, e um exemplo rápido da flexibilidade de "código é dados":Desde que você possa seguir as regras de sintaxe, estender esse idioma será, na maioria das vezes, nada mais do que definir novas funções. Alguns usuários estão suportando recursos do Rebol 3 no Rebol 2, por exemplo.
fonte
Ruby tem uma sintaxe bastante flexível, acho que é uma maneira de "estender a linguagem dentro da própria linguagem".
Um exemplo é o rake . Está escrito em Ruby, é Ruby, mas parece make .
Para verificar algumas possibilidades, você pode procurar pelas palavras-chave Ruby e metaprogramação .
fonte
Estender a sintaxe da maneira como você está falando permite criar idiomas específicos do domínio. Portanto, talvez a maneira mais útil de reformular sua pergunta seja: quais outros idiomas têm um bom suporte para idiomas específicos de domínio?
Ruby tem uma sintaxe muito flexível, e muitas DSLs floresceram lá, como rake. Groovy inclui muito dessa bondade. Ele também inclui transformações AST, que são mais diretamente análogas às macros Lisp.
R, a linguagem para computação estatística, permite que as funções obtenham seus argumentos não avaliados. Ele usa isso para criar uma DSL para especificar a fórmula de regressão. Por exemplo:
significa "ajustar uma linha da forma k0 + k1 * a + k2 * b aos valores em y".
significa "ajustar uma linha da forma k0 + k1 * a + k2 * b + k3 * a * b aos valores em y".
E assim por diante.
fonte
Converge é outra linguagem de metaprogramação não lispy. E, até certo ponto, o C ++ também se qualifica.
Indiscutivelmente, o MetaOCaml está bem longe do Lisp. Para um estilo totalmente diferente de extensibilidade de sintaxe, mas ainda bastante poderoso, dê uma olhada no CamlP4 .
Nemerle é outra linguagem extensível com uma metaprogramação no estilo Lisp, embora seja mais próxima de linguagens como Scala.
E, em breve , o próprio Scala também se tornará uma linguagem desse tipo.
Edit: Eu esqueci o exemplo mais interessante - JetBrains MPS . Ele não é apenas muito distante de qualquer coisa do Lispish, é também um sistema de programação não textual, com um editor operando diretamente no nível AST.
Edit2: Para responder a uma pergunta atualizada - não há nada de único e excepcional nas macros do Lisp. Em teoria, qualquer linguagem pode fornecer esse mecanismo (eu até fiz isso com C simples). Tudo o que você precisa é de acesso ao seu AST e capacidade de executar código em tempo de compilação. Alguma reflexão pode ajudar (consultar sobre os tipos, as definições existentes etc.).
fonte
O Prolog permite definir novos operadores que são traduzidos para termos compostos com o mesmo nome. Por exemplo, isso define um
has_cat
operador e o define como um predicado para verificar se uma lista contém o átomocat
:O
xf
meio quehas_cat
é um operador postfix; usandofx
o tornaria um operador de prefixo exfx
o tornaria um operador de infixo, recebendo dois argumentos. Verifique este link para obter mais detalhes sobre a definição de operadores no Prolog.fonte
O TeX está totalmente ausente da lista. Vocês todos sabem, certo? Parece algo como isto:
… Exceto que você pode redefinir a sintaxe sem restrições. Cada token (!) No idioma pode receber um novo significado. O ConTeXt é um pacote de macro que substituiu os chavetas por chavetas:
O pacote macro mais comum LaTeX também redefine o idioma para seus fins, por exemplo, adicionando o
\begin{environment}…\end{environment}
sintaxe.Mas não para por aí. Tecnicamente, você também pode redefinir os tokens para analisar o seguinte:
Sim, absolutamente possível. Alguns pacotes usam isso para definir pequenos idiomas específicos do domínio. Por exemplo, o pacote TikZ define uma sintaxe concisa para desenhos técnicos, que permite o seguinte:
Além disso, o TeX é o Turing completo para que você possa literalmente fazer tudo com ele. Eu nunca vi isso usado em todo o seu potencial, porque seria muito inútil e muito complicado, mas é perfeitamente possível tornar o código a seguir analisável apenas redefinindo os tokens (mas isso provavelmente iria para os limites físicos do analisador, devido ao como é construído):
fonte
O Boo permite que você personalize o idioma intensamente em tempo de compilação por meio de macros sintáticas.
O Boo tem um "pipeline de compilador extensível". Isso significa que o compilador pode chamar seu código para fazer transformações AST a qualquer momento durante o pipeline do compilador. Como você sabe, coisas como Genéricos do Java ou Linq do C # são apenas transformações de sintaxe em tempo de compilação, portanto isso é bastante poderoso.
Comparado ao Lisp, a principal vantagem é que isso funciona com qualquer tipo de sintaxe. O Boo está usando uma sintaxe inspirada em Python, mas você provavelmente poderia escrever um compilador extensível com uma sintaxe C ou Pascal. E como a macro é avaliada em tempo de compilação, não há penalidade no desempenho.
As desvantagens, em comparação com o Lisp, são:
Por exemplo, é assim que você pode implementar uma nova estrutura de controle:
uso:
que é traduzido, em tempo de compilação, para algo como:
(Infelizmente, a documentação on-line do Boo está sempre irremediavelmente desatualizada e nem cobre itens avançados como este. A melhor documentação para o idioma que conheço é este livro: http://www.manning.com/rahien/ )
fonte
A avaliação do Mathematica é baseada na correspondência e substituição de padrões. Isso permite criar suas próprias estruturas de controle, alterar estruturas de controle existentes ou alterar a maneira como as expressões são avaliadas. Por exemplo, você pode implementar "lógica difusa" assim (um pouco simplificada):
Isso substitui a avaliação dos operadores lógicos predefinidos &&, || ,! e a
If
cláusula incorporada .Você pode ler essas definições como definições de função, mas o significado real é: Se uma expressão corresponder ao padrão descrito no lado esquerdo, ela será substituída pela expressão no lado direito. Você pode definir sua própria cláusula If como esta:
SetAttributes[..., HoldRest]
informa ao avaliador que ele deve avaliar o primeiro argumento antes da correspondência do padrão, mas mantenha a avaliação pelo resto até depois que o padrão for correspondido e substituído.Isso é amplamente utilizado nas bibliotecas padrão do Mathematica para, por exemplo, definir uma função
D
que pega uma expressão e avalia sua derivada simbólica.fonte
O Metalua é uma linguagem e um compilador compatível com Lua que fornece isso.
Diferenças com o Lisp:
Um exemplo de aplicação é a implementação da correspondência de padrões do tipo ML.
Veja também: http://lua-users.org/wiki/MetaLua
fonte
Se você está procurando idiomas extensíveis, deve dar uma olhada no Smalltalk.
No Smalltalk, a única maneira de programar é realmente estender o idioma. Não há diferença entre o IDE, as bibliotecas ou a própria linguagem. Eles estão todos tão entrelaçados que o Smalltalk é frequentemente referido como um ambiente e não como um idioma.
Você não escreve aplicativos independentes no Smalltalk; em vez disso, estende o ambiente de linguagem.
Confira http://www.world.st/ para obter alguns recursos e informações.
Eu gostaria de recomendar o Pharo como o dialeto de entrada no mundo do Smalltalk: http://pharo-project.org
Espero que tenha ajudado!
fonte
Existem ferramentas que permitem criar linguagens personalizadas sem escrever um compilador inteiro do zero. Por exemplo, há Spoofax , que é uma ferramenta de transformação de código: você coloca regras de gramática e transformação de entrada (escritas de maneira declarativa de nível muito alto) e, em seguida, pode gerar o código-fonte Java (ou outra linguagem, se você se interessar o suficiente) de um idioma personalizado criado por você.
Portanto, seria possível pegar a gramática da linguagem X, definir a gramática da linguagem X '(X com suas extensões personalizadas) e transformar X' → X, e o Spoofax gerará um compilador X '→ X.
Atualmente, se eu entendi direito, o melhor suporte é para Java, com o suporte a C # sendo desenvolvido (ou pelo menos ouvi dizer). Essa técnica pode ser aplicada a qualquer idioma com gramática estática (portanto, por exemplo, provavelmente não Perl ).
fonte
Quarto é outro idioma que é altamente extensível. Muitas implementações do Forth consistem em um pequeno kernel escrito em assembler ou C, e o restante do idioma é escrito no próprio Forth.
Existem também vários idiomas baseados em pilha que são inspirados pelo Forth e compartilham esse recurso, como o Factor .
fonte
Funge-98
O recurso de impressão digital do Funge-98 permite que possa ser feito para permitir uma reestruturação completa de toda a sintaxe e semântica da linguagem. Mas somente se o implementador fornecer um mecanismo de impressão digital que permita ao usuário alterar programaticamente o idioma (isso é teoricamente possível de implementar na sintaxe e na semântica normais do Funge-98). Nesse caso, alguém poderia literalmente fazer com que o restante do arquivo (ou qualquer parte do arquivo) aja como C ++ ou Lisp (ou o que ele quiser).
http://quadium.net/funge/spec98.html#Fingerprints
fonte
Para obter o que você está procurando, você realmente precisa desses parênteses e falta de sintaxe. Algumas linguagens baseadas em sintaxe podem se aproximar, mas não é exatamente a mesma coisa que uma macro verdadeira.
fonte