Preciso de ajuda para entender alguns dos pontos de What Made Lisp Different, de Paul Graham .
Um novo conceito de variáveis. No Lisp, todas as variáveis são efetivamente ponteiras. Os valores são o que têm tipos, não variáveis, e atribuir ou vincular variáveis significa copiar ponteiros, não o que eles apontam.
Um tipo de símbolo. Os símbolos diferem das strings, pois você pode testar a igualdade comparando um ponteiro.
Uma notação para código usando árvores de símbolos.
Todo o idioma sempre disponível. Não há distinção real entre tempo de leitura, tempo de compilação e tempo de execução. Você pode compilar ou executar código durante a leitura, ler ou executar código durante a compilação e ler ou compilar código em tempo de execução.
O que esses pontos significam? Como eles são diferentes em linguagens como C ou Java? Algum outro idioma além dos idiomas da família Lisp possui alguma dessas construções agora?
paul-graham
etiqueta aqui? !!! Ótimo ...Respostas:
A explicação de Matt é perfeitamente boa - e ele faz uma comparação com C e Java, o que eu não farei -, mas por alguma razão eu realmente gosto de discutir esse mesmo tópico de vez em quando, então - aqui está minha chance em uma resposta.
Nos pontos (3) e (4):
Os pontos (3) e (4) da sua lista parecem os mais interessantes e ainda relevantes agora.
Para entendê-los, é útil ter uma imagem clara do que acontece com o código Lisp - na forma de um fluxo de caracteres digitados pelo programador - a caminho de ser executado. Vamos usar um exemplo concreto:
Este trecho de código Clojure é impresso
aFOObFOOcFOO
. Observe que o Clojure provavelmente não satisfaz totalmente o quarto ponto da sua lista, pois o tempo de leitura não está realmente aberto ao código do usuário; Vou discutir o que significaria que isso não fosse o caso.Então, suponha que tenhamos esse código em um arquivo em algum lugar e solicitamos ao Clojure para executá-lo. Além disso, vamos supor (por uma questão de simplicidade) que superamos a importação da biblioteca. A parte interessante começa em
(println
e termina na extremidade)
direita. Isso é lexado / analisado como seria de esperar, mas já surge um ponto importante: o resultado não é uma representação AST específica do compilador - é apenas uma estrutura de dados Clojure / Lisp regular , ou seja, uma lista aninhada que contém vários símbolos, strings e - nesse caso - um único objeto de padrão regex compilado correspondente ao#"\d+"
literal (mais sobre isso abaixo). Alguns Lisps adicionam suas próprias reviravoltas a esse processo, mas Paul Graham estava se referindo principalmente ao Common Lisp. Nos pontos relevantes para sua pergunta, Clojure é semelhante ao CL.O idioma inteiro no momento da compilação:
Após esse ponto, todo o compilador lida (isso também seria verdadeiro para um intérprete Lisp; o código Clojure sempre é compilado) são estruturas de dados Lisp que os programadores Lisp estão acostumados a manipular. Nesse momento, uma possibilidade maravilhosa se torna aparente: por que não permitir que os programadores Lisp escrevam funções Lisp que manipulam dados Lisp que representam programas Lisp e produzem dados transformados representando programas transformados, para serem usados no lugar dos originais? Em outras palavras - por que não permitir que programadores Lisp registrem suas funções como plugins de compilador, chamados macros no Lisp? E, de fato, qualquer sistema Lisp decente tem essa capacidade.
Portanto, macros são funções regulares do Lisp operando na representação do programa em tempo de compilação, antes da fase final de compilação, quando o código real do objeto é emitido. Como não há limites para os tipos de macros de código que podem ser executados (em particular, o código que eles executam costuma ser escrito com o uso liberal do recurso de macros), pode-se dizer que "todo o idioma está disponível em tempo de compilação "
O idioma inteiro no momento da leitura:
Vamos voltar a esse
#"\d+"
regex literal. Como mencionado acima, isso é transformado em um objeto padrão compilado real no tempo de leitura, antes que o compilador ouça a primeira menção de novo código sendo preparado para compilação. Como isso acontece?Bem, da maneira como o Clojure é atualmente implementado, a imagem é um pouco diferente da que Paul Graham tinha em mente, embora tudo seja possível com um truque inteligente . Em Common Lisp, a história seria um pouco mais limpa conceitualmente. No entanto, o básico é semelhante: o Lisp Reader é uma máquina de estado que, além de realizar transições de estado e eventualmente declarar se alcançou um "estado de aceitação", cospe estruturas de dados do Lisp que os caracteres representam. Assim, os caracteres
123
se tornam o número,123
etc. O ponto importante agora é: esta máquina de estados pode ser modificada pelo código do usuário. (Como observado anteriormente, isso é inteiramente verdade no caso de CL; para Clojure, é necessário um hack (desencorajado e não usado na prática). Mas discordo, é o artigo de PG que devo estar elaborando, então ...)Portanto, se você é um programador do Common Lisp e gosta da ideia de literais vetoriais no estilo Clojure, basta conectar ao leitor uma função para reagir adequadamente a alguma sequência de caracteres -
[
ou#[
possivelmente - e tratá-la como o início de um literal de vetor que termina na correspondência]
. Essa função é chamada de macro de leitor e, assim como uma macro comum, pode executar qualquer tipo de código Lisp, incluindo código que foi escrito com uma notação divertida ativada por macros de leitor registradas anteriormente. Portanto, há todo o idioma no momento da leitura para você.Embrulhando-o:
Na verdade, o que foi demonstrado até agora é que é possível executar funções regulares do Lisp em tempo de leitura ou compilação; o único passo que é necessário dar a partir daqui para entender como a leitura e a compilação são possíveis no tempo de leitura, compilação ou execução é perceber que a leitura e a compilação são elas mesmas executadas pelas funções do Lisp. Você pode simplesmente ligar
read
oueval
a qualquer momento para ler os dados Lisp dos fluxos de caracteres ou compilar e executar o código Lisp, respectivamente. Essa é toda a linguagem ali, o tempo todo.Observe como o fato de o Lisp satisfazer o ponto (3) da sua lista é essencial para a maneira como ele consegue satisfazer o ponto (4) - o sabor específico das macros fornecidas pelo Lisp depende muito do código ser representado pelos dados regulares do Lisp, que é algo ativado por (3). Aliás, apenas o aspecto "tree-ish" do código é realmente crucial aqui - você pode ter um Lisp escrito usando XML.
fonte
1) Um novo conceito de variáveis. No Lisp, todas as variáveis são efetivamente ponteiras. Os valores são o que têm tipos, não variáveis, e atribuir ou vincular variáveis significa copiar ponteiros, não o que eles apontam.
'it' é uma variável. Pode ser associado a QUALQUER valor. Não há restrição e nenhum tipo associado à variável. Se você chamar a função, o argumento não precisará ser copiado. A variável é semelhante a um ponteiro. Tem uma maneira de acessar o valor que está vinculado à variável. Não há necessidade de reservar memória. Podemos passar qualquer objeto de dados quando chamamos a função: qualquer tamanho e qualquer tipo.
Os objetos de dados têm um 'tipo' e todos os objetos de dados podem ser consultados por seu 'tipo'.
2) Um tipo de símbolo. Os símbolos diferem das strings, pois você pode testar a igualdade comparando um ponteiro.
Um símbolo é um objeto de dados com um nome. Normalmente, o nome pode ser usado para encontrar o objeto:
Como os símbolos são objetos de dados reais, podemos testar se são o mesmo objeto:
Isso nos permite, por exemplo, escrever uma frase com símbolos:
Agora podemos contar o número de THE na frase:
No Common Lisp, os símbolos não apenas têm um nome, mas também podem ter um valor, uma função, uma lista de propriedades e um pacote. Portanto, os símbolos podem ser usados para nomear variáveis ou funções. A lista de propriedades geralmente é usada para adicionar metadados aos símbolos.
3) Uma notação para código usando árvores de símbolos.
O Lisp usa suas estruturas de dados básicas para representar o código.
A lista (* 3 2) pode ser tanto dados quanto código:
A árvore:
4) Todo o idioma sempre disponível. Não há distinção real entre tempo de leitura, tempo de compilação e tempo de execução. Você pode compilar ou executar código durante a leitura, ler ou executar código durante a compilação e ler ou compilar código em tempo de execução.
O Lisp fornece as funções READ para ler dados e código do texto, LOAD para carregar o código, EVAL para avaliar o código, COMPILE para compilar código e PRINT para gravar dados e código no texto.
Essas funções estão sempre disponíveis. Eles não vão embora. Eles podem fazer parte de qualquer programa. Isso significa que qualquer programa pode ler, carregar, avaliar ou imprimir código - sempre.
Como eles são diferentes em linguagens como C ou Java?
Esses idiomas não fornecem símbolos, código como dados ou avaliação de tempo de execução dos dados como código. Os objetos de dados em C geralmente não são digitados.
Algum outro idioma além dos idiomas da família LISP possui alguma dessas construções agora?
Muitos idiomas possuem alguns desses recursos.
A diferença:
No Lisp, esses recursos são projetados na linguagem para facilitar o uso.
fonte
Para os pontos (1) e (2), ele está falando historicamente. As variáveis de Java são praticamente as mesmas, e é por isso que você precisa chamar .equals () para comparar valores.
(3) está falando sobre expressões S. Os programas Lisp são escritos nesta sintaxe, o que oferece muitas vantagens sobre sintaxe ad-hoc como Java e C, como capturar padrões repetidos em macros de uma maneira muito mais limpa que as macros C ou modelos C ++ e manipular código com a mesma lista principal operações que você usa para dados.
(4) tomando C, por exemplo: a linguagem é realmente duas sub-línguas diferentes: coisas como if () e while (), e o pré-processador. Você usa o pré-processador para evitar ter que se repetir o tempo todo ou para pular código com # if / # ifdef. Mas os dois idiomas são bem separados, e você não pode usar while () em tempo de compilação como você pode #if.
O C ++ torna isso ainda pior com os modelos. Confira algumas referências sobre a metaprogramação de modelos, que fornece uma maneira de gerar código em tempo de compilação e é extremamente difícil para quem não é especialista. Além disso, é realmente um monte de truques e truques usando modelos e macros que o compilador não pode fornecer suporte de primeira classe - se você cometer um erro de sintaxe simples, o compilador não poderá fornecer uma mensagem de erro clara.
Bem, com o Lisp, você tem tudo isso em um único idioma. Você usa o mesmo material para gerar código em tempo de execução ao aprender em seu primeiro dia. Isso não significa que a metaprogramação seja trivial, mas certamente é mais direta com o suporte de linguagem e compilador de primeira classe.
fonte
Os pontos (1) e (2) também se encaixariam no Python. Tomando um exemplo simples "a = str (82.4)", o intérprete primeiro cria um objeto de ponto flutuante com o valor 82.4. Em seguida, chama um construtor de strings que retorna uma string com o valor '82 .4 '. O 'a' no lado esquerdo é apenas um rótulo para esse objeto de string. O objeto de ponto flutuante original foi coletado como lixo porque não há mais referências a ele.
No esquema, tudo é tratado como um objeto de maneira semelhante. Não tenho certeza sobre o Common Lisp. Eu tentaria evitar pensar em termos de conceitos de C / C ++. Eles me abrandaram quando eu tentava entender a bela simplicidade de Lisps.
fonte