De acordo com este artigo, a seguinte linha de código Lisp imprime "Hello world" na saída padrão.
(format t "hello, world")
O Lisp, que é uma linguagem homoicônica , pode tratar o código como dados da seguinte maneira:
Agora imagine que escrevemos a seguinte macro:
(defmacro backwards (expr) (reverse expr))
para trás é o nome da macro, que pega uma expressão (representada como uma lista) e a inverte. Aqui está "Olá, mundo" novamente, desta vez usando a macro:
(backwards ("hello, world" t format))
Quando o compilador Lisp vê essa linha de código, ele olha para o primeiro átomo na lista (
backwards
) e percebe que ele nomeia uma macro. Ele passa a lista não avaliada("hello, world" t format)
para a macro, que reorganiza a lista para(format t "hello, world")
. A lista resultante substitui a expressão de macro e é o que será avaliado em tempo de execução. O ambiente Lisp verá que seu primeiro átomo (format
) é uma função e a avaliará, passando o restante dos argumentos.
No Lisp, é fácil realizar essa tarefa (corrija-me se estiver errado) porque o código é implementado como lista ( expressões-s ?).
Agora dê uma olhada neste trecho do OCaml (que não é uma linguagem homoicônica):
let print () =
let message = "Hello world" in
print_endline message
;;
Imagine que você deseja adicionar homoiconicidade ao OCaml, que usa uma sintaxe muito mais complexa em comparação com o Lisp. Como você faria isso? A linguagem precisa ter uma sintaxe particularmente fácil para alcançar a homoiconicidade?
EDIT : a partir deste tópico , encontrei outra maneira de obter homoiconicidade diferente da Lisp: a implementada na linguagem io . Pode parcialmente responder a esta pergunta.
Aqui, vamos começar com um bloco simples:
Io> plus := block(a, b, a + b) ==> method(a, b, a + b ) Io> plus call(2, 3) ==> 5
Ok, então o bloco funciona. O bloco positivo adicionou dois números.
Agora vamos fazer uma introspecção sobre esse pequeno companheiro.
Io> plus argumentNames ==> list("a", "b") Io> plus code ==> block(a, b, a +(b)) Io> plus message name ==> a Io> plus message next ==> +(b) Io> plus message next name ==> +
Molde frio santo quente. Você não pode apenas obter os nomes dos parâmetros do bloco. E você não apenas pode obter uma sequência do código fonte completo do bloco. Você pode se infiltrar no código e percorrer as mensagens dentro. E o mais surpreendente de tudo: é incrivelmente fácil e natural. Fiel à missão de Io. O espelho de Ruby não pode ver nada disso.
Mas, whoa whoa, ei agora, não toque nesse botão.
Io> plus message next setName("-") ==> -(b) Io> plus ==> method(a, b, a - b ) Io> plus call(2, 3) ==> -1
Respostas:
Você pode tornar qualquer idioma homoicônico. Essencialmente, você faz isso 'espelhando' a linguagem (ou seja, para qualquer construtor de linguagem, você adiciona uma representação correspondente desse construtor como dados, pense em AST). Você também precisa adicionar algumas operações adicionais, como citar e não citar. É mais ou menos isso.
O Lisp teve isso desde o início por causa de sua sintaxe fácil, mas a família de idiomas MetaML de W. Taha mostrou que é possível fazer isso em qualquer idioma.
Todo o processo é descrito em Modelando metaprogramação generativa homogênea . Aqui está uma introdução mais leve ao mesmo material .
fonte
O compilador Ocaml é escrito no próprio Ocaml; portanto, certamente existe uma maneira de manipular os ASTs do Ocaml no Ocaml.
Pode-se imaginar adicionar um tipo embutido
ocaml_syntax
ao idioma e ter umadefmacro
função embutida, que recebe uma entrada do tipo, digamosAgora, qual é o tipo de
defmacro
? Bem, isso realmente depende da entrada, pois, mesmo quef
seja a função de identidade, o tipo da parte do código resultante depende da parte da sintaxe transmitida.Esse problema não surge no lisp, pois o idioma é digitado dinamicamente e nenhum tipo precisa ser atribuído à própria macro no momento da compilação. Uma solução seria ter
o que permitiria que a macro fosse usada em qualquer contexto. Mas isso é inseguro, é claro, permitiria que um
bool
a fosse usado em vez de umstring
, travando o programa em tempo de execução.A única solução baseada em princípios em uma linguagem de tipo estatico seria ter tipos dependentes nos quais o tipo de resultado
defmacro
seria dependente da entrada. As coisas ficam bastante complicadas neste momento, e eu começaria apontando a agradável dissertação de David Raymond Christiansen.Concluindo: ter uma sintaxe complicada não é um problema, pois há muitas maneiras de representar a sintaxe dentro da linguagem e, possivelmente, usar a metaprogramação como uma
quote
operação para incorporar a sintaxe "simples" ao internoocaml_syntax
.O problema está tornando isso bem digitado, principalmente com um mecanismo de macro em tempo de execução que não permite erros de tipo.
É claro que é possível ter um mecanismo de tempo de compilação para macros em uma linguagem como o Ocaml, veja, por exemplo, MetaOcaml .
Também possivelmente útil: Jane street em metaprogramação em Ocaml
fonte
Como exemplo, considere F # (baseado em OCaml). O F # não é totalmente homoicônico, mas suporta a obtenção do código de uma função como AST sob certas circunstâncias.
Em F #, você
print
seria representado como umExpr
impresso como:Para destacar melhor a estrutura, aqui está uma maneira alternativa de como você pode criar a mesma
Expr
:fonte
eval(<string>)
função? ( De acordo com muitos recursos, tendo função eval é diferente de ter homoiconicity - é a razão pela qual você disse F # não é totalmente homoiconic?)print
com o[<ReflectedDefinition>]
atributo.)