Homoiconicidade - Homoiconicity

Na programação de computadores , homoiconicity (do grego palavras homo , que significa "o mesmo" e ícone que significa "representação") é uma propriedade de algumas linguagens de programação . Uma linguagem é homoicônica se um programa escrito nela pode ser manipulado como dados usando a linguagem e, portanto, a representação interna do programa pode ser inferida apenas pela leitura do próprio programa. Essa propriedade geralmente é resumida dizendo que a linguagem trata "código como dados".

Em uma linguagem homoicônica, a representação primária de programas também é uma estrutura de dados em um tipo primitivo da própria linguagem. Isso torna a metaprogramação mais fácil do que em uma linguagem sem esta propriedade: a reflexão na linguagem (examinando as entidades do programa em tempo de execução ) depende de uma estrutura única e homogênea e não precisa lidar com várias estruturas diferentes que apareceriam em uma sintaxe complexa. Linguagens homoicônicas normalmente incluem suporte total a macros sintáticas , permitindo que o programador expresse as transformações de programas de forma concisa.

Um exemplo comumente citado é Lisp , que foi criado para permitir manipulações de lista fáceis e onde a estrutura é fornecida por expressões S que tomam a forma de listas aninhadas e podem ser manipuladas por outro código Lisp. Outros exemplos são as linguagens de programação Clojure (um dialeto contemporâneo do Lisp), Rebol (também seu sucessor Red ), Refal , Prolog e, mais recentemente, Julia .

História

A fonte original é o artigo Macro Instruction Extensions of Compiler Languages , de acordo com o artigo anterior e influente TRAC, A Text-Handling Language :

Um dos principais objetivos do projeto era que o script de entrada do TRAC (o que é digitado pelo usuário) fosse idêntico ao texto que orienta a ação interna do processador TRAC. Em outras palavras, os procedimentos TRAC devem ser armazenados na memória como uma seqüência de caracteres exatamente como o usuário os digitou no teclado. Se os próprios procedimentos do TRAC desenvolverem novos procedimentos, esses novos procedimentos também devem ser declarados no mesmo script. O processador TRAC em sua ação interpreta este script como seu programa. Em outras palavras, o programa tradutor TRAC (o processador) converte efetivamente o computador em um novo computador com uma nova linguagem de programa - a linguagem TRAC. A qualquer momento, deve ser possível exibir as informações do programa ou do procedimento da mesma forma que o processador TRAC agirá durante sua execução. É desejável que a representação do código de caractere interno seja idêntica ou muito semelhante à representação do código externo. Na presente implementação do TRAC, a representação de caracteres internos é baseada em ASCII . Como os procedimentos e texto TRAC têm a mesma representação dentro e fora do processador, o termo homoicônico é aplicável, de homo significando o mesmo e ícone significando representação.

[...]

Seguindo a sugestão de McCullough, WS , baseada na terminologia de Peirce, CS McIlroy. MD, "Macro Instruction Extensions of Compiler Languages," Comm. ACM, p. 214–220; Abril de 1960.

Alan Kay usou e possivelmente popularizou o termo "homoicônico" por meio de seu uso do termo em sua tese de doutorado de 1969:

Um grupo notável de exceções a todos os sistemas anteriores são Interactive LISP [...] e TRAC. Ambos são orientados funcionalmente (uma lista, a outra string), ambos falam com o usuário em um idioma e ambos são "homoicônicos" no sentido de que suas representações internas e externas são essencialmente as mesmas. Ambos têm a capacidade de criar dinamicamente novas funções que podem então ser elaboradas de acordo com o prazer do usuário. Sua única grande desvantagem é que os programas escritos neles se parecem com a carta do rei Burniburiach aos sumérios escrita em cuniforme babilônico! [...]

Usos e vantagens

Uma vantagem da homoiconicidade é que estender a linguagem com novos conceitos normalmente se torna mais simples, pois os dados que representam o código podem ser passados ​​entre a meta e a camada base do programa. A árvore de sintaxe abstrata de uma função pode ser composta e manipulada como uma estrutura de dados na metamada e então avaliada . Pode ser muito mais fácil entender como manipular o código, pois pode ser mais facilmente entendido como dados simples (já que o formato da linguagem em si é um formato de dados).

Uma demonstração típica de homoiconicidade é o avaliador metacircular .

Métodos de implementação

Todos os sistemas de arquitetura Von Neumann , que incluem a grande maioria dos computadores de uso geral hoje, podem ser descritos implicitamente como homoicônicos devido à maneira como o código de máquina bruto é executado na memória, sendo o tipo de dados bytes na memória. No entanto, esse recurso também pode ser abstraído no nível da linguagem de programação.

Linguagens como Lisp e seus dialetos, como Scheme , Clojure e Racket empregam expressões S para atingir a homoiconicidade.

Outras línguas consideradas homoicônicas incluem:

Em Lisp

Lisp usa expressões S como uma representação externa para dados e código. Expressões S podem ser lidas com a função Lisp primitiva READ. READretorna dados Lisp: listas, símbolos , números, strings. A função Lisp primitiva EVALusa código Lisp representado como dados Lisp, calcula os efeitos colaterais e retorna um resultado. O resultado será impresso pela função primitiva PRINT, que cria uma expressão S externa a partir de dados Lisp.

Dados Lisp, uma lista que usa diferentes tipos de dados: (sub) listas, símbolos, strings e números inteiros.

((:name "john" :age 20) (:name "mary" :age 18) (:name "alice" :age 22))

Código Lisp. O exemplo usa listas, símbolos e números.

(* (sin 1.1) (cos 2.03))      ; in infix: sin(1.1)*cos(2.03)

Crie a expressão acima com a função Lisp primitiva LISTe defina a variável EXPRESSIONpara o resultado

(setf expression  (list '* (list 'sin 1.1) (list 'cos 2.03)) )  
-> (* (SIN 1.1) (COS 2.03))    ; Lisp returns and prints the result

(third expression)    ; the third element of the expression
-> (COS 2.03)

Altere o COStermo paraSIN

(setf (first (third expression)) 'SIN)
; The expression is now (* (SIN 1.1) (SIN 2.03)).

Avalie a expressão

(eval expression)
-> 0.7988834

Imprime a expressão em uma string

(print-to-string expression)
->  "(* (SIN 1.1) (SIN 2.03))"

Leia a expressão de uma string

(read-from-string "(* (SIN 1.1) (SIN 2.03))")
->  (* (SIN 1.1) (SIN 2.03))     ; returns a list of lists, numbers and symbols

Em Prolog

1 ?- X is 2*5.
X = 10.

2 ?- L = (X is 2*5), write_canonical(L).
is(_, *(2, 5))
L = (X is 2*5).

3 ?- L = (ten(X):-(X is 2*5)), write_canonical(L).
:-(ten(A), is(A, *(2, 5)))
L = (ten(X):-X is 2*5).

4 ?- L = (ten(X):-(X is 2*5)), assert(L).
L = (ten(X):-X is 2*5).

5 ?- ten(X).
X = 10.

6 ?-

Na linha 4, criamos uma nova cláusula. O operador :-separa a cabeça e o corpo de uma cláusula. Com o assert/1*adicionamos às cláusulas existentes (adicionamos ao "banco de dados"), para que possamos chamá-lo mais tarde. Em outras linguagens, nós o chamaríamos de "criar uma função durante o tempo de execução". Também podemos remover cláusulas do banco de dados com abolish/1ou retract/1.

* O número após o nome da cláusula é o número de argumentos que ela pode receber. Também é chamado de aridade .

Também podemos consultar o banco de dados para obter o corpo de uma cláusula:

7 ?- clause(ten(X),Y).
Y = (X is 2*5).

8 ?- clause(ten(X),Y), Y = (X is Z).
Y = (X is 2*5),
Z = 2*5.

9 ?- clause(ten(X),Y), call(Y).
X = 10,
Y = (10 is 2*5).

callé análogo à evalfunção do Lisp .

Em Rebol

O conceito de tratamento de código como dados e a manipulação e avaliação dos mesmos podem ser demonstrados de forma muito clara no Rebol . (Rebol, ao contrário do Lisp, não requer parênteses para separar expressões).

A seguir está um exemplo de código em Rebol (observe que >>representa o prompt do interpretador; espaços entre alguns elementos foram adicionados para facilitar a leitura):

>> repeat i 3 [ print [ i "hello" ] ]

1 hello
2 hello
3 hello

( repeaté de fato uma função embutida no Rebol e não é uma construção de linguagem ou palavra-chave).

Ao colocar o código entre colchetes, o intérprete não o avalia, mas apenas o trata como um bloco contendo palavras:

[ repeat i 3 [ print [ i "hello" ] ] ]

Este bloco tem o tipo de bloco! e pode, além disso, ser atribuído como o valor de uma palavra usando o que parece ser uma sintaxe para atribuição, mas é realmente entendido pelo intérprete como um tipo especial ( set-word!) e assume a forma de uma palavra seguida por dois pontos:

>> block1: [ repeat i 3 [ print [ i "hello" ] ] ] ;; Assign the value of the block to the word `block1`
== [repeat i 3 [print [i "hello"]]]
>> type? block1 ;; Evaluate the type of the word `block1`
== block!

O bloco ainda pode ser interpretado usando a dofunção fornecida no Rebol (semelhante ao evalLisp ).

É possível interrogar os elementos do bloco e alterar seus valores, alterando assim o comportamento do código se ele for avaliado:

>> block1/3 ;; The third element of the block
== 3
>> block1/3: 5 ;; Set the value of the 3rd element to 5
== 5
>> probe block1 ;; Show the changed block
== [repeat i 5 [print [i "hello"]]]
>> do block1 ;; Evaluate the block
1 hello
2 hello
3 hello
4 hello
5 hello

Veja também

Referências

links externos