Lisp Comum - Common Lisp

Lisp Comum
Paradigma Multi-paradigma : procedimental , funcional , orientado a objetos , meta , reflexivo , genérico
Família Lisp
Projetado por Scott Fahlman , Richard P. Gabriel , David A. Moon , Kent Pitman , Guy Steele , Dan Weinreb
Desenvolvedor ANSI X3J13 comitê
Apareceu pela primeira vez 1984 (37 anos atrás) , 1994 (27 anos atrás) para ANSI Common Lisp ( 1984 ) ( 1994 )
Disciplina de digitação Dinâmico , forte
Alcance Lexical, opcionalmente dinâmico
SO Plataforma cruzada
Extensões de nome de arquivo .lisp, .lsp, .l, .cl, .fasl
Local na rede Internet common-lisp .net
Implementações principais
Allegro CL , ABCL , CLISP , Clozure CL , CMUCL , ECL , GCL , LispWorks , Scieneer CL , SBCL , Symbolics Common Lisp
Dialetos
CLtL1, CLtL2, ANSI Common Lisp
Influenciado por
Lisp , Lisp Máquina Lisp , Maclisp , Scheme , Interlisp
Influenciado
Clojure , Dylan , Emacs Lisp , EuLisp , ISLISP , * Lisp , AutoLisp , Julia , Moose , R , SKILL , SubL

Common Lisp ( CL ) é um dialeto da linguagem de programação Lisp , publicado em ANSI documento padrão ANSI INCITS 226-1994 (S20018) (anteriormente X3.226-1994 (R1999) ). O Common Lisp HyperSpec , uma versão HTML com hiperlink, foi derivado do padrão ANSI Common Lisp.

A linguagem Common Lisp foi desenvolvida como uma sucessora padronizada e aprimorada de Maclisp . No início dos anos 1980, vários grupos já estavam trabalhando em diversos sucessores de MacLisp: Lisp Machine Lisp (também conhecido como ZetaLisp), Spice Lisp , NIL e S-1 Lisp . O Common Lisp buscou unificar, padronizar e estender os recursos desses dialetos MacLisp. Common Lisp não é uma implementação, mas sim uma especificação de linguagem . Várias implementações do padrão Common Lisp estão disponíveis, incluindo software livre e de código aberto e produtos proprietários. Common Lisp é uma linguagem de programação multiparadigma de propósito geral . Ele oferece suporte a uma combinação de paradigmas de programação procedural , funcional e orientada a objetos . Como uma linguagem de programação dinâmica , ela facilita o desenvolvimento de software evolutivo e incremental , com compilação iterativa em programas de tempo de execução eficientes. Esse desenvolvimento incremental geralmente é feito de forma interativa, sem interromper o aplicativo em execução.

Ele também suporta anotação de tipo opcional e conversão, que podem ser adicionados conforme necessário nas fases posteriores de criação de perfil e otimização, para permitir que o compilador gere um código mais eficiente. Por exemplo, fixnumpode conter um inteiro não encaixotado em um intervalo suportado pelo hardware e implementação, permitindo aritmética mais eficiente do que em números inteiros grandes ou tipos de precisão arbitrária. Da mesma forma, o compilador pode ser informado por módulo ou por função sobre o tipo de nível de segurança desejado, usando declarações de otimização .

Common Lisp inclui CLOS , um sistema de objetos que suporta multimétodos e combinações de métodos. Geralmente é implementado com um protocolo de metaobjeto .

Common Lisp é extensível por meio de recursos padrão, como macros Lisp (transformações de código) e macros de leitura (analisadores de entrada para caracteres).

Common Lisp fornece compatibilidade parcial com versões anteriores com Maclisp e Lisp original de John McCarthy . Isso permite que software Lisp mais antigo seja portado para Common Lisp.

História

O trabalho no Common Lisp começou em 1981 após uma iniciativa do gerente do ARPA Bob Engelmore para desenvolver um único dialeto Lisp padrão da comunidade. Muito do design inicial da linguagem foi feito por meio de correio eletrônico. Em 1982, Guy L. Steele Jr. deu a primeira visão geral do Common Lisp no Simpósio ACM de 1982 sobre LISP e programação funcional.

A primeira documentação da linguagem foi publicada em 1984 como Common Lisp the Language (conhecido como CLtL1), primeira edição. Uma segunda edição (conhecida como CLtL2), publicada em 1990, incorporou muitas mudanças na linguagem, feitas durante o processo de padronização do ANSI Common Lisp: sintaxe LOOP estendida, o Common Lisp Object System, o Condition System para tratamento de erros, uma interface para o impressora bonita e muito mais. Mas CLtL2 não descreve o padrão ANSI Common Lisp final e, portanto, não é uma documentação do ANSI Common Lisp. O padrão ANSI Common Lisp final foi então publicado em 1994. Desde então, nenhuma atualização do padrão foi publicada. Várias extensões e melhorias para Common Lisp (exemplos são Unicode, Concurrency, CLOS-based IO) foram fornecidas por implementações e bibliotecas.

Sintaxe

Lisp comum é um dialeto do Lisp. Ele usa expressões S para denotar a estrutura de código e de dados. Chamadas de função, macro formulários e formulários especiais são escritos como listas, com o nome do operador primeiro, como nestes exemplos:

 (+ 2 2)           ; adds 2 and 2, yielding 4. The function's name is '+'. Lisp has no operators as such.
 (defvar *x*)      ; Ensures that a variable *x* exists,
                   ; without giving it a value. The asterisks are part of
                   ; the name, by convention denoting a special (global) variable. 
                   ; The symbol *x* is also hereby endowed with the property that
                   ; subsequent bindings of it are dynamic, rather than lexical.
 (setf *x* 42.1)   ; Sets the variable *x* to the floating-point value 42.1
 ;; Define a function that squares a number:
 (defun square (x)
   (* x x))
 ;; Execute the function:
 (square 3)        ; Returns 9
 ;; The 'let' construct creates a scope for local variables. Here
 ;; the variable 'a' is bound to 6 and the variable 'b' is bound
 ;; to 4. Inside the 'let' is a 'body', where the last computed value is returned.
 ;; Here the result of adding a and b is returned from the 'let' expression.
 ;; The variables a and b have lexical scope, unless the symbols have been
 ;; marked as special variables (for instance by a prior DEFVAR).
 (let ((a 6)
       (b 4))
   (+ a b))        ; returns 10

Tipos de dados

Lisp comum tem muitos tipos de dados .

Tipos escalares

Número tipos incluem inteiros , proporções , números de ponto flutuante , e números complexos . Common Lisp usa bignums para representar valores numéricos de tamanho e precisão arbitrários. O tipo de proporção representa exatamente as frações, um recurso não disponível em muitos idiomas. O Common Lisp força automaticamente os valores numéricos entre esses tipos, conforme apropriado.

O tipo de caractere Common Lisp não está limitado a caracteres ASCII . A maioria das implementações modernas permite caracteres Unicode .

O tipo de símbolo é comum às linguagens Lisp, mas amplamente desconhecido fora delas. Um símbolo é um objeto de dados nomeado exclusivo com várias partes: nome, valor, função, lista de propriedades e pacote. Destes, a célula de valor e a célula de função são as mais importantes. Símbolos em Lisp são freqüentemente usados ​​de forma semelhante a identificadores em outras linguagens: para manter o valor de uma variável; no entanto, existem muitos outros usos. Normalmente, quando um símbolo é avaliado, seu valor é retornado. Alguns símbolos são avaliados por si próprios, por exemplo, todos os símbolos no pacote de palavras-chave são autoavaliados. Os valores booleanos em Common Lisp são representados pelos símbolos de autoavaliação T e NIL. Common Lisp possui namespaces para símbolos, chamados 'pacotes'.

Várias funções estão disponíveis para arredondar valores numéricos escalares de várias maneiras. A função roundarredonda o argumento para o número inteiro mais próximo, com casos intermediários arredondados para o número inteiro par. As funções truncate, floore são ceilingarredondadas para zero, para baixo ou para cima, respectivamente. Todas essas funções retornam a parte fracionária descartada como um valor secundário. Por exemplo, (floor -2.5)produz −3, 0,5; (ceiling -2.5)produz −2, −0,5; (round 2.5)produz 2, 0,5; e (round 3.5)produz 4, −0,5.

Estruturas de dados

Os tipos de sequência no Common Lisp incluem listas, vetores, vetores de bits e strings. Existem muitas operações que podem funcionar em qualquer tipo de sequência.

Como em quase todos os outros dialetos Lisp, as listas em Common Lisp são compostas de conses , às vezes chamados de células cons ou pares . Um contras é uma estrutura de dados com dois slots, chamados de carro e cdr . Uma lista é uma cadeia ligada de conses ou a lista vazia. Cada carro dos contras refere-se a um membro da lista (possivelmente outra lista). Cada cdr de contras se refere às próximas contras - exceto para as últimas contras em uma lista, cujo cdr se refere ao nilvalor. Conses também podem ser facilmente usados ​​para implementar árvores e outras estruturas de dados complexas; embora seja geralmente aconselhável usar instâncias de estrutura ou classe. Também é possível criar estruturas de dados circulares com contras.

Common Lisp oferece suporte a matrizes multidimensionais e pode redimensionar matrizes ajustáveis dinamicamente, se necessário. Matrizes multidimensionais podem ser usadas para matemática de matrizes. Um vetor é uma matriz unidimensional. Os arrays podem carregar qualquer tipo como membros (até mesmo tipos mistos no mesmo array) ou podem ser especializados para conter um tipo específico de membros, como em um vetor de bits. Normalmente, apenas alguns tipos são suportados. Muitas implementações podem otimizar funções de array quando o array usado é especializado em tipo. Dois tipos de matriz especializado do tipo padrão são: uma cadeia é um vector de caracteres, ao passo que um bit-vector é um vector de pedaços .

As tabelas de hash armazenam associações entre objetos de dados. Qualquer objeto pode ser usado como chave ou valor. As tabelas de hash são redimensionadas automaticamente conforme necessário.

Pacotes são coleções de símbolos, usados ​​principalmente para separar as partes de um programa em namespaces . Um pacote pode exportar alguns símbolos, marcando-os como parte de uma interface pública. Os pacotes podem usar outros pacotes.

Estruturas , semelhantes em uso a estruturas C e registros Pascal , representam estruturas de dados complexas arbitrárias com qualquer número e tipo de campos (chamados slots ). Estruturas permitem herança única.

As classes são semelhantes às estruturas, mas oferecem recursos mais dinâmicos e herança múltipla. (Veja CLOS ). As classes foram adicionadas posteriormente ao Common Lisp e há alguma sobreposição conceitual com estruturas. Objetos criados de classes são chamados de Instâncias . Um caso especial são as Funções Genéricas. Funções genéricas são funções e instâncias.

Funções

Common Lisp suporta funções de primeira classe . Por exemplo, é possível escrever funções que recebem outras funções como argumentos ou funções de retorno também. Isso torna possível descrever operações muito gerais.

A biblioteca Common Lisp depende muito dessas funções de ordem superior. Por exemplo, a sortfunção recebe um operador relacional como um argumento e a função chave como um argumento de palavra-chave opcional. Isso pode ser usado não apenas para classificar qualquer tipo de dados, mas também para classificar estruturas de dados de acordo com uma chave.

 ;; Sorts the list using the > and < function as the relational operator.
 (sort (list 5 2 6 3 1 4) #'>)   ; Returns (6 5 4 3 2 1)
 (sort (list 5 2 6 3 1 4) #'<)   ; Returns (1 2 3 4 5 6)
 ;; Sorts the list according to the first element of each sub-list.
 (sort (list '(9 A) '(3 B) '(4 C)) #'< :key #'first)   ; Returns ((3 B) (4 C) (9 A))

O modelo de avaliação de funções é muito simples. Quando o avaliador encontra um formulário (f a1 a2...), ele presume que o símbolo denominado f é um dos seguintes:

  1. Um operador especial (facilmente verificado em uma lista fixa)
  2. Um operador macro (deve ter sido definido anteriormente)
  3. O nome de uma função (padrão), que pode ser um símbolo ou um subformulário que começa com o símbolo lambda.

Se f for o nome de uma função, os argumentos a1, a2, ..., an são avaliados da esquerda para a direita e a função é encontrada e chamada com os valores fornecidos como parâmetros.

Definindo funções

A macrodefun define funções em que uma definição de função fornece o nome da função, os nomes de quaisquer argumentos e um corpo de função:

 (defun square (x)
   (* x x))

As definições de função podem incluir diretivas de compilador , conhecidas como declarações , que fornecem dicas ao compilador sobre as configurações de otimização ou os tipos de dados dos argumentos. Eles também podem incluir strings de documentação (docstrings), que o sistema Lisp pode usar para fornecer documentação interativa:

 (defun square (x)
   "Calculates the square of the single-float x."
   (declare (single-float x) (optimize (speed 3) (debug 0) (safety 1)))
   (the single-float (* x x)))

Funções anônimas ( literais de função ) são definidas usando lambdaexpressões, por exemplo, (lambda (x) (* x x))para uma função que quadrada seu argumento. O estilo de programação Lisp freqüentemente usa funções de ordem superior para as quais é útil fornecer funções anônimas como argumentos.

As funções locais podem ser definidas com flete labels.

 (flet ((square (x)
          (* x x)))
   (square 3))

Existem vários outros operadores relacionados à definição e manipulação de funções. Por exemplo, uma função pode ser compilada com o compileoperador. (Alguns sistemas Lisp executam funções usando um interpretador por padrão, a menos que sejam instruídos a compilar; outros compilam todas as funções).

Definição de funções e métodos genéricos

A macro defgenericdefine funções genéricas . Funções genéricas são uma coleção de métodos . A macro defmethoddefine métodos.

Métodos podem especializar seus parâmetros sobre CLOS classes padrão , classes de sistema , classes de estrutura ou objetos individuais. Para muitos tipos, existem classes de sistema correspondentes .

Quando uma função genérica é chamada, o envio múltiplo determinará o método eficaz a ser usado.

 (defgeneric add (a b))
 (defmethod add ((a number) (b number))
   (+ a b))
 (defmethod add ((a vector) (b number))
   (map 'vector (lambda (n) (+ n b)) a))
 (defmethod add ((a vector) (b vector))
   (map 'vector #'+ a b))
(defmethod add ((a string) (b string))
  (concatenate 'string a b))
 (add 2 3)                   ; returns 5
 (add #(1 2 3 4) 7)          ; returns #(8 9 10 11)
 (add #(1 2 3 4) #(4 3 2 1)) ; returns #(5 5 5 5)
 (add "COMMON " "LISP")      ; returns "COMMON LISP"

Funções genéricas também são um tipo de dados de primeira classe . Existem muito mais recursos para Funções e Métodos Genéricos do que os descritos acima.

O namespace da função

O namespace para nomes de funções é separado do namespace para variáveis ​​de dados. Esta é uma diferença chave entre Common Lisp e Scheme . Para Lisp comum, operadores que definem nomes no namespace função incluem defun, flet, labels, defmethode defgeneric.

Para passar uma função por nome como um argumento para outra função, deve-se usar o functionoperador especial, comumente abreviado como #'. O primeiro sortexemplo acima se refere à função nomeada pelo símbolo >no namespace da função, com o código #'>. Por outro lado, para chamar uma função passada dessa forma, seria usado o funcalloperador no argumento.

O modelo de avaliação do Scheme é mais simples: há apenas um namespace e todas as posições no formulário são avaliadas (em qualquer ordem) - não apenas os argumentos. O código escrito em um dialeto é, portanto, às vezes confuso para programadores mais experientes no outro. Por exemplo, muitos programadores do Common Lisp gostam de usar nomes de variáveis ​​descritivos, como lista ou string, que podem causar problemas no Scheme, pois fariam localmente ocultar nomes de funções.

Se um namespace separado para funções é uma vantagem, é uma fonte de contenção na comunidade Lisp. É normalmente referido como o debate Lisp-1 vs. Lisp-2 . Lisp-1 se refere ao modelo de Scheme e Lisp-2 se refere ao modelo de Common Lisp. Esses nomes foram cunhados em um artigo de 1988 por Richard P. Gabriel e Kent Pitman , que compara extensivamente as duas abordagens.

Vários valores de retorno

Common Lisp suporta o conceito de múltiplos valores , onde qualquer expressão sempre tem um único valor primário , mas também pode ter qualquer número de valores secundários , que podem ser recebidos e inspecionados por chamadores interessados. Esse conceito é diferente de retornar um valor de lista, pois os valores secundários são totalmente opcionais e passados ​​por meio de um canal lateral dedicado. Isso significa que os chamadores podem permanecer totalmente inconscientes da existência de valores secundários, se não precisarem deles, e torna conveniente usar o mecanismo de comunicação de informações que às vezes é útil, mas nem sempre necessário. Por exemplo,

  • A TRUNCATEfunção arredonda o número fornecido para um inteiro em direção a zero. No entanto, ele também retorna um resto como um valor secundário, tornando muito fácil determinar qual valor foi truncado. Ele também suporta um parâmetro divisor opcional, que pode ser usado para realizar a divisão euclidiana trivialmente:
(let ((x 1266778)
      (y 458))
  (multiple-value-bind (quotient remainder)
      (truncate x y)
    (format nil "~A divided by ~A is ~A remainder ~A" x y quotient remainder)))

;;;; => "1266778 divided by 458 is 2765 remainder 408"
  • GETHASHretorna o valor de uma chave em um mapa associativo , ou o valor padrão caso contrário, e um booleano secundário indicando se o valor foi encontrado. Assim, o código que não se importa se o valor foi encontrado ou fornecido como padrão pode simplesmente usá-lo no estado em que se encontra, mas quando tal distinção for importante, ele pode inspecionar o booleano secundário e reagir apropriadamente. Ambos os casos de uso são suportados pela mesma chamada e nenhum é desnecessariamente sobrecarregado ou restringido pelo outro. Ter esse recurso no nível do idioma elimina a necessidade de verificar a existência da chave ou compará-la com o valor nulo, como seria feito em outros idiomas.
(defun get-answer (library)
  (gethash 'answer library 42))

(defun the-answer-1 (library)
  (format nil "The answer is ~A" (get-answer library)))
;;;; Returns "The answer is 42" if ANSWER not present in LIBRARY

(defun the-answer-2 (library)
  (multiple-value-bind (answer sure-p)
      (get-answer library)
    (if (not sure-p)
        "I don't know"
     (format nil "The answer is ~A" answer))))
;;;; Returns "I don't know" if ANSWER not present in LIBRARY

Vários valores são suportados por um punhado de formulários padrão, os mais comuns dos quais são o MULTIPLE-VALUE-BINDformulário especial para acessar valores secundários e VALUESpara retornar vários valores:

(defun magic-eight-ball ()
  "Return an outlook prediction, with the probability as a secondary value"
  (values "Outlook good" (random 1.0)))

;;;; => "Outlook good"
;;;; => 0.3187

Outros tipos

Outros tipos de dados em Common Lisp incluem:

  • Os nomes de caminho representam arquivos e diretórios no sistema de arquivos . O recurso de nome de caminho Common Lisp é mais geral do que as convenções de nomenclatura de arquivos da maioria dos sistemas operacionais, tornando o acesso dos programas Lisp a arquivos amplamente portável em diversos sistemas.
  • Os fluxos de entrada e saída representam fontes e coletores de dados binários ou textuais, como o terminal ou arquivos abertos.
  • O Common Lisp possui um gerador de números pseudo-aleatórios (PRNG) integrado . Objetos de estado aleatório representam fontes reutilizáveis ​​de números pseudo-aleatórios, permitindo ao usuário propagar o PRNG ou fazer com que ele reproduza uma sequência.
  • Condições são um tipo usado para representar erros, exceções e outros eventos "interessantes" aos quais um programa pode responder.
  • As classes são objetos de primeira classe e, elas próprias, instâncias de classes chamadas classes de metaobjetos ( metaclasses para abreviar).
  • Readtables são um tipo de objeto que controla como o leitor do Common Lisp analisa o texto do código-fonte. Ao controlar qual legível está em uso quando o código é lido, o programador pode alterar ou estender a sintaxe da linguagem.

Alcance

Como programas em muitas outras linguagens de programação, os programas Common Lisp usam nomes para se referir a variáveis, funções e muitos outros tipos de entidades. As referências nomeadas estão sujeitas ao escopo.

A associação entre um nome e a entidade à qual o nome se refere é chamada de ligação.

O escopo se refere ao conjunto de circunstâncias nas quais um nome é determinado como tendo uma ligação específica.

Determinantes de escopo

As circunstâncias que determinam o escopo em Common Lisp incluem:

  • a localização de uma referência em uma expressão. Se for a posição mais à esquerda de um composto, ele se refere a um operador especial ou a uma macro ou vinculação de função, caso contrário, a uma vinculação de variável ou outra coisa.
  • o tipo de expressão em que a referência ocorre. Por exemplo, (go x)significa que transfere o controle para o rótulo x, enquanto (print x)se refere à variável x. Ambos os escopos de xpodem estar ativos na mesma região do texto do programa, uma vez que os rótulos de tagbody estão em um namespace separado dos nomes de variáveis. Uma forma especial ou forma de macro tem controle total sobre os significados de todos os símbolos em sua sintaxe. Por exemplo, em (defclass x (a b) ()), uma definição de classe, o (a b)é uma lista de classes base, então esses nomes são procurados no espaço de nomes de classes e xnão são uma referência a uma associação existente, mas o nome de uma nova classe sendo derivada de ae b. Esses fatos emergem puramente da semântica de defclass. O único fato genérico sobre essa expressão é que defclassse refere a uma ligação de macro; tudo o mais depende defclass.
  • a localização da referência dentro do texto do programa. Por exemplo, se uma referência à variável xestiver incluída em um construto de ligação, como um letque define uma ligação para x, então a referência está no escopo criado por essa ligação.
  • para uma referência de variável, se um símbolo de variável foi ou não, local ou globalmente, declarado especial. Isso determina se a referência é resolvida em um ambiente léxico ou em um ambiente dinâmico.
  • a instância específica do ambiente em que a referência é resolvida. Um ambiente é um dicionário de tempo de execução que mapeia símbolos para ligações. Cada tipo de referência usa seu próprio tipo de ambiente. As referências a variáveis ​​lexicais são resolvidas em um ambiente lexical, etc. Mais de um ambiente pode ser associado à mesma referência. Por exemplo, graças à recursão ou ao uso de vários threads, várias ativações da mesma função podem existir ao mesmo tempo. Essas ativações compartilham o mesmo texto de programa, mas cada uma tem sua própria instância de ambiente lexical.

Para entender a que um símbolo se refere, o programador do Common Lisp deve saber que tipo de referência está sendo expressa, que tipo de escopo ela usa se for uma referência de variável (escopo dinâmico versus léxico), e também a situação de tempo de execução: em qual ambiente é a referência resolvida, onde a ligação foi introduzida no ambiente, etc.

Tipos de ambiente

Global

Alguns ambientes em Lisp são globalmente difundidos. Por exemplo, se um novo tipo for definido, ele será conhecido em todos os lugares a partir de então. As referências a esse tipo podem ser encontradas neste ambiente global.

Dinâmico

Um tipo de ambiente em Common Lisp é o ambiente dinâmico. As ligações estabelecidas neste ambiente têm extensão dinâmica, o que significa que uma ligação é estabelecida no início da execução de alguma construção, como um letbloco, e desaparece quando essa construção termina de ser executada: seu tempo de vida está vinculado à ativação e desativação dinâmica de um bloco. No entanto, uma vinculação dinâmica não é apenas visível dentro desse bloco; também é visível para todas as funções invocadas daquele bloco. Esse tipo de visibilidade é conhecido como escopo indefinido. Ligações que exibem extensão dinâmica (tempo de vida vinculado à ativação e desativação de um bloco) e escopo indefinido (visível para todas as funções que são chamadas desse bloco) são consideradas como tendo escopo dinâmico.

O Common Lisp tem suporte para variáveis ​​de escopo dinâmico, que também são chamadas de variáveis ​​especiais. Certos outros tipos de vínculos também têm necessariamente escopo dinâmico, como reinicializações e tags de captura. Os vínculos de função não podem ter escopo dinâmico usando flet(que fornece apenas vínculos de função com escopo léxico), mas os objetos de função (um objeto de primeiro nível no Common Lisp) podem ser atribuídos a variáveis ​​com escopo dinâmico, vinculados usando letno escopo dinâmico e, em seguida, chamados usando funcallou APPLY.

O escopo dinâmico é extremamente útil porque adiciona clareza referencial e disciplina às variáveis ​​globais . Variáveis ​​globais são desaprovadas na ciência da computação como fontes potenciais de erro, porque podem dar origem a canais de comunicação ad-hoc e secretos entre módulos que levam a interações indesejadas e surpreendentes.

Em Common Lisp, uma variável especial que possui apenas uma associação de nível superior se comporta como uma variável global em outras linguagens de programação. Um novo valor pode ser armazenado nele e esse valor simplesmente substitui o que está na ligação de nível superior. A substituição descuidada do valor de uma variável global está no cerne dos bugs causados ​​pelo uso de variáveis ​​globais. No entanto, outra maneira de trabalhar com uma variável especial é fornecer a ela uma nova vinculação local dentro de uma expressão. Isso às vezes é chamado de "religação" da variável. Vincular uma variável com escopo dinâmico cria temporariamente um novo local de memória para essa variável e associa o nome a esse local. Enquanto essa associação estiver em vigor, todas as referências a essa variável se referem à nova associação; a ligação anterior está oculta. Quando a execução da expressão de ligação termina, o local da memória temporária desaparece e a ligação antiga é revelada, com o valor original intacto. Obviamente, várias ligações dinâmicas para a mesma variável podem ser aninhadas.

Em implementações Common Lisp que suportam multithreading, escopos dinâmicos são específicos para cada thread de execução. Assim, as variáveis ​​especiais servem como uma abstração para o armazenamento local do thread. Se um thread religar uma variável especial, essa religação não terá efeito sobre essa variável em outros threads. O valor armazenado em uma ligação só pode ser recuperado pelo encadeamento que criou essa ligação. Se cada thread vincula alguma variável especial *x*, então *x*se comporta como armazenamento local de thread. Entre os encadeamentos que não são religados *x*, ele se comporta como um global comum: todos esses encadeamentos referem-se à mesma ligação de nível superior de *x*.

Variáveis ​​dinâmicas podem ser usadas para estender o contexto de execução com informações de contexto adicionais que são passadas implicitamente de função para função sem ter que aparecer como um parâmetro de função extra. Isso é especialmente útil quando a transferência de controle precisa passar por camadas de código não relacionado, que simplesmente não podem ser estendidas com parâmetros extras para passar os dados adicionais. Uma situação como essa geralmente exige uma variável global. Essa variável global deve ser salva e restaurada, para que o esquema não seja interrompido por recursão: a religação da variável dinâmica cuida disso. E essa variável deve ser feita no local do segmento (ou então um grande mutex deve ser usado) para que o esquema não seja interrompido sob os segmentos: as implementações de escopo dinâmico também podem cuidar disso.

Na biblioteca Common Lisp, existem muitas variáveis ​​especiais padrão. Por exemplo, todos os fluxos de E / S padrão são armazenados nas ligações de nível superior de variáveis ​​especiais conhecidas. O fluxo de saída padrão é armazenado em * saída padrão *.

Suponha que uma função foo grave na saída padrão:

  (defun foo ()
    (format t "Hello, world"))

Para capturar sua saída em uma string de caracteres, * standard-output * pode ser vinculado a um stream de string e chamado:

  (with-output-to-string (*standard-output*)
    (foo))
 -> "Hello, world" ; gathered output returned as a string

Lexical

Lisp comum suporta ambientes lexicais. Formalmente, as ligações em um ambiente léxico têm escopo léxico e podem ter uma extensão indefinida ou dinâmica, dependendo do tipo de namespace. Âmbito lexical significa que a visibilidade está fisicamente restrita ao bloco em que a encadernação é estabelecida. As referências que não são textualmente (ou seja, lexicamente) embutidas nesse bloco simplesmente não veem essa ligação.

As tags em um TAGBODY têm escopo léxico. A expressão (GO X) é errônea se não estiver embutida em um TAGBODY que contém um rótulo X. No entanto, as associações de rótulos desaparecem quando o TAGBODY encerra sua execução, porque têm extensão dinâmica. Se esse bloco de código for inserido novamente pela invocação de um fechamento léxico , é inválido para o corpo desse fechamento tentar transferir o controle para uma tag via GO:

  (defvar *stashed*) ;; will hold a function

  (tagbody
    (setf *stashed* (lambda () (go some-label)))
    (go end-label) ;; skip the (print "Hello")
   some-label
    (print "Hello")
   end-label)
  -> NIL

Quando o TAGBODY é executado, ele primeiro avalia a forma setf que armazena uma função na variável especial * escondida *. Então o (go end-label) transfere o controle para o end-label, pulando o código (imprima "Hello"). Uma vez que end-label está no final do tagbody, o tagbody termina, produzindo NIL. Suponha que a função lembrada anteriormente agora seja chamada:

  (funcall *stashed*) ;; Error!

Esta situação é errônea. A resposta de uma implementação é uma condição de erro que contém a mensagem, "GO: tagbody para a tag SOME-LABEL já foi deixada". A função tentou avaliar (go some-label), que está lexicamente embutido no tagbody e resolve para o rótulo. No entanto, o tagbody não está executando (sua extensão terminou) e, portanto, a transferência de controle não pode ocorrer.

As associações de função local no Lisp têm escopo léxico e as associações de variáveis ​​também têm escopo léxico por padrão. Em contraste com os rótulos GO, ambos têm extensão indefinida. Quando uma função lexical ou ligação variável é estabelecida, essa ligação continua a existir enquanto as referências a ela forem possíveis, mesmo após o construto que estabeleceu essa ligação ter terminado. Referências a variáveis ​​e funções lexicais após o término de sua construção de estabelecimento são possíveis graças a fechamentos lexicais .

A vinculação lexical é o modo de vinculação padrão para variáveis ​​Common Lisp. Para um símbolo individual, ele pode ser alternado para o escopo dinâmico, seja por uma declaração local, seja por uma declaração global. O último pode ocorrer implicitamente por meio do uso de uma construção como DEFVAR ou DEFPARAMETER. É uma convenção importante na programação Common Lisp que variáveis ​​especiais (isto é, com escopo dinâmico) tenham nomes que começam e terminam com um símbolo de asterisco *no que é chamado de " convenção de proteção para os ouvidos ". Se cumprida, essa convenção cria efetivamente um namespace separado para variáveis ​​especiais, de modo que as variáveis ​​que pretendem ser lexicais não sejam acidentalmente tornadas especiais.

O escopo lexical é útil por vários motivos.

Em primeiro lugar, as referências a variáveis ​​e funções podem ser compiladas para um código de máquina eficiente, porque a estrutura do ambiente de tempo de execução é relativamente simples. Em muitos casos, ele pode ser otimizado para empilhar o armazenamento, portanto, abrir e fechar escopos lexicais tem sobrecarga mínima. Mesmo nos casos em que é necessário gerar fechamentos totais, o acesso ao ambiente do fechamento ainda é eficiente; normalmente, cada variável se torna um deslocamento em um vetor de ligações e, portanto, uma referência de variável torna-se uma instrução de carga ou armazenamento simples com um modo de endereçamento base mais deslocamento .

Em segundo lugar, o escopo lexical (combinado com extensão indefinida) dá origem ao fechamento lexical , que por sua vez cria todo um paradigma de programação centrado em torno do uso de funções como objetos de primeira classe, que está na raiz da programação funcional.

Em terceiro lugar, talvez o mais importante, mesmo que os fechamentos lexicais não sejam explorados, o uso do escopo lexical isola os módulos do programa de interações indesejadas. Devido à sua visibilidade restrita, as variáveis ​​lexicais são privadas. Se um módulo A vincula uma variável lexical X e chama outro módulo B, as referências a X em B não resolverão acidentalmente o limite X em A. B simplesmente não tem acesso a X. Para situações em que as interações disciplinadas por meio de uma variável são desejável, Common Lisp fornece variáveis ​​especiais. Variáveis ​​especiais permitem que um módulo A estabeleça uma ligação para uma variável X que é visível para outro módulo B, chamado de A. Poder fazer isso é uma vantagem, e poder evitar que isso aconteça também é uma vantagem; conseqüentemente, Common Lisp suporta escopo léxico e dinâmico .

Macros

Uma macro em Lisp se parece superficialmente com uma função em uso. No entanto, em vez de representar uma expressão que é avaliada, representa uma transformação do código-fonte do programa. A macro obtém a fonte que cerca como argumentos, vincula-os aos seus parâmetros e calcula uma nova forma de fonte. Este novo formulário também pode usar uma macro. A expansão da macro é repetida até que o novo formulário de origem não use uma macro. A forma final computada é o código-fonte executado em tempo de execução.

Usos típicos de macros em Lisp:

  • novas estruturas de controle (exemplo: construções de loop, construções de ramificação)
  • escopo e construtos de ligação
  • sintaxe simplificada para código-fonte complexo e repetido
  • formulários de definição de nível superior com efeitos colaterais em tempo de compilação
  • programação baseada em dados
  • linguagens específicas de domínio incorporado (exemplos: SQL , HTML , Prolog )
  • formulários de finalização implícita

Vários recursos padrão do Common Lisp também precisam ser implementados como macros, como:

  • a setfabstração padrão , para permitir expansões personalizadas em tempo de compilação de operadores de atribuição / acesso
  • with-accessors, with-slots, with-open-fileE outros semelhantes WITHmacros
  • Dependendo da implementação, ifou condé uma macro construída na outra, o operador especial; whene unlessconsistem em macros
  • A poderosa looplinguagem específica de domínio

As macros são definidas pela macro defmacro . O operador especial macrolet permite a definição de macros locais (com escopo léxico). Também é possível definir macros para símbolos usando define-symbol-macro e symbol-macrolet .

O livro On Lisp de Paul Graham descreve o uso de macros em Common Lisp em detalhes. O livro Let Over Lambda de Doug Hoyte estende a discussão sobre macros, afirmando que "As macros são a maior vantagem que o lisp tem como linguagem de programação e a maior vantagem de qualquer linguagem de programação." Hoyte fornece vários exemplos de desenvolvimento iterativo de macros.

Exemplo de uso de macro para definir uma nova estrutura de controle

As macros permitem que os programadores Lisp criem novas formas sintáticas na linguagem. Um uso típico é criar novas estruturas de controle. A macro de exemplo fornece uma untilconstrução de loop. A sintaxe é:

(until test form*)

A definição macro para até :

(defmacro until (test &body body)
  (let ((start-tag (gensym "START"))
        (end-tag   (gensym "END")))
    `(tagbody ,start-tag
              (when ,test (go ,end-tag))
              (progn ,@body)
              (go ,start-tag)
              ,end-tag)))

tagbody é um operador especial Common Lisp primitivo que fornece a capacidade de nomear tags e usar o formulário go para pular para essas tags. O acento grave ` fornece uma notação que proporciona modelos de código, onde o valor de formas precedida com uma vírgula são preenchidos. Formas precedida com vírgula e no-sinal são emendados em. A forma tagbody testa a condição final. Se a condição for verdadeira, ele pula para a tag final. Caso contrário, o código do corpo fornecido é executado e, em seguida, salta para a tag de início.

Um exemplo de uso da macro até acima :

(until (= (random 10) 0)
  (write-line "Hello"))

O código pode ser expandido usando a função macroexpand-1 . A expansão do exemplo acima se parece com isto:

(TAGBODY
 #:START1136
 (WHEN (ZEROP (RANDOM 10))
   (GO #:END1137))
 (PROGN (WRITE-LINE "hello"))
 (GO #:START1136)
 #:END1137)

Durante a expansão da macro, o valor do teste da variável é (= (aleatório 10) 0) e o valor do corpo da variável é ((linha de escrita "Olá")) . O corpo é uma lista de formulários.

Os símbolos geralmente são eliminados automaticamente. A expansão usa o TAGBODY com duas etiquetas. Os símbolos para essas etiquetas são calculados por GENSYM e não estão incluídos em nenhum pacote. Dois formulários go usam essas tags para acessar . Visto que tagbody é um operador primitivo em Common Lisp (e não uma macro), ele não será expandido para outra coisa. A forma expandida usa a macro quando , que também será expandida. Expandir totalmente um formulário de origem é chamado de code walking .

Na forma totalmente expandida ( percorrida ), a forma quando é substituída pela primitiva se :

(TAGBODY
 #:START1136
 (IF (ZEROP (RANDOM 10))
     (PROGN (GO #:END1137))
   NIL)
 (PROGN (WRITE-LINE "hello"))
 (GO #:START1136))
 #:END1137)

Todas as macros devem ser expandidas antes que o código-fonte que as contém possa ser avaliado ou compilado normalmente. As macros podem ser consideradas funções que aceitam e retornam expressões S - semelhantes às árvores de sintaxe abstratas , mas não se limitam a elas. Essas funções são chamadas antes do avaliador ou compilador para produzir o código-fonte final. As macros são escritas em Common Lisp normal e podem usar qualquer operador Common Lisp (ou de terceiros) disponível.

Captura variável e sombreamento

As macros Lisp comuns são capazes do que é comumente chamado de captura de variável , onde os símbolos no corpo da macroexpansão coincidem com aqueles no contexto de chamada, permitindo ao programador criar macros em que vários símbolos têm um significado especial. O termo captura de variável é um tanto enganoso, porque todos os namespaces são vulneráveis ​​à captura indesejada, incluindo o namespace de operador e função, o namespace de rótulo de tagbody, tag catch, manipulador de condição e namespaces de reinicialização.

A captura variável pode apresentar defeitos de software. Isso acontece de uma das seguintes maneiras:

  • Na primeira forma, uma expansão de macro pode inadvertidamente fazer uma referência simbólica que o criador da macro presumiu que resolverá em um namespace global, mas o código onde a macro é expandida fornece uma definição local sombreada que rouba essa referência. Que isso seja referido como captura do tipo 1.
  • A segunda maneira, a captura do tipo 2, é exatamente o oposto: alguns dos argumentos da macro são pedaços de código fornecidos pelo chamador da macro, e esses pedaços de código são escritos de forma que façam referências às ligações circundantes. No entanto, a macro insere esses pedaços de código em uma expansão que define suas próprias ligações que capturam acidentalmente algumas dessas referências.

O dialeto Scheme do Lisp fornece um sistema de escrita de macro que fornece a transparência referencial que elimina ambos os tipos de problema de captura. Este tipo de macrossistema é por vezes denominado "higiénico", em particular pelos seus proponentes (que consideram os macrossistemas que não resolvem automaticamente este problema como anti-higiénicos).

No Common Lisp, a macro higiene é garantida de duas maneiras diferentes.

Uma abordagem é usar academias : símbolos exclusivos garantidos que podem ser usados ​​em uma expansão macro sem ameaça de captura. O uso de academias em uma definição de macro é uma tarefa manual, mas macros podem ser escritas, o que simplifica a instanciação e o uso de academias. As academias resolvem a captura do tipo 2 facilmente, mas não são aplicáveis ​​à captura do tipo 1 da mesma maneira, porque a expansão da macro não pode renomear os símbolos de interferência no código circundante que capturam suas referências. Os ginásios podem ser usados ​​para fornecer apelidos estáveis ​​para os símbolos globais necessários à expansão macro. A expansão da macro usaria esses apelidos secretos em vez dos nomes conhecidos, portanto, a redefinição dos nomes conhecidos não teria nenhum efeito prejudicial na macro.

Outra abordagem é usar pacotes. Uma macro definida em seu próprio pacote pode simplesmente usar símbolos internos nesse pacote em sua expansão. O uso de pacotes trata da captura do tipo 1 e do tipo 2.

No entanto, os pacotes não resolvem a captura tipo 1 de referências a funções e operadores Common Lisp padrão. A razão é que o uso de pacotes para resolver problemas de captura gira em torno do uso de símbolos privados (símbolos em um pacote, que não são importados para, ou de outra forma tornados visíveis em outros pacotes). Considerando que os símbolos da biblioteca Common Lisp são externos e freqüentemente importados ou tornados visíveis em pacotes definidos pelo usuário.

A seguir está um exemplo de captura indesejada no namespace do operador, ocorrendo na expansão de uma macro:

 ;; expansion of UNTIL makes liberal use of DO
 (defmacro until (expression &body body)
   `(do () (,expression) ,@body))

 ;; macrolet establishes lexical operator binding for DO
 (macrolet ((do (...) ... something else ...))
   (until (= (random 10) 0) (write-line "Hello")))

A untilmacro se expandirá em uma forma que chama doque se destina a se referir à macro Common Lisp padrão do. No entanto, neste contexto, dopode ter um significado completamente diferente, portanto, untilpode não funcionar corretamente.

Common Lisp resolve o problema do sombreamento de operadores e funções padrão proibindo sua redefinição. Como ele redefine o operador padrão do, o precedente é na verdade um fragmento do Common Lisp não conforme, que permite que as implementações o diagnostiquem e rejeitem.

Sistema de condição

O sistema de condição é responsável pelo tratamento de exceções no Common Lisp. Ele fornece condições , manipuladores e reinicializações . As condições são objetos que descrevem uma situação excepcional (por exemplo, um erro). Se uma condição for sinalizada, o sistema Common Lisp procura um manipulador para esse tipo de condição e chama o manipulador. O manipulador agora pode pesquisar reinicializações e usar uma dessas reinicializações para reparar automaticamente o problema atual, usando informações como o tipo de condição e qualquer informação relevante fornecida como parte do objeto de condição e chamar a função de reinicialização apropriada.

Essas reinicializações, se não tratadas por código, podem ser apresentadas aos usuários (como parte de uma interface de usuário, a de um depurador, por exemplo), para que o usuário possa selecionar e chamar uma das reinicializações disponíveis. Uma vez que o tratador de condição é chamado no contexto do erro (sem desfazer a pilha), a recuperação total do erro é possível em muitos casos, onde outros sistemas de tratamento de exceção já teriam encerrado a rotina atual. O próprio depurador também pode ser personalizado ou substituído usando a *debugger-hook*variável dinâmica. O código encontrado em formulários de proteção de desenrolamento , como finalizadores, também será executado conforme apropriado, apesar da exceção.

No exemplo a seguir (usando o Symbolics Genera ) o usuário tenta abrir um arquivo em um teste de função Lisp chamado de Read-Eval-Print-LOOP ( REPL ), quando o arquivo não existe. O sistema Lisp apresenta quatro reinicializações. O usuário seleciona Repetir ABRIR usando uma reinicialização de nome de caminho diferente e insere um nome de caminho diferente (lispm-init.lisp em vez de lispm-int.lisp). O código do usuário não contém nenhum código de tratamento de erros. Todo o tratamento de erros e o código de reinicialização são fornecidos pelo sistema Lisp, que pode tratar e reparar o erro sem encerrar o código do usuário.

Command: (test ">zippy>lispm-int.lisp")

Error: The file was not found.
       For lispm:>zippy>lispm-int.lisp.newest

LMFS:OPEN-LOCAL-LMFS-1
   Arg 0: #P"lispm:>zippy>lispm-int.lisp.newest"

s-A, <Resume>: Retry OPEN of lispm:>zippy>lispm-int.lisp.newest
s-B:           Retry OPEN using a different pathname
s-C, <Abort>:  Return to Lisp Top Level in a TELNET server
s-D:           Restart process TELNET terminal

-> Retry OPEN using a different pathname
Use what pathname instead [default lispm:>zippy>lispm-int.lisp.newest]:
   lispm:>zippy>lispm-init.lisp.newest

...the program continues

Common Lisp Object System (CLOS)

Common Lisp inclui um kit de ferramentas para programação orientada a objetos , o Common Lisp Object System ou CLOS , que é um dos mais poderosos sistemas de objetos disponíveis em qualquer linguagem. Por exemplo, Peter Norvig explica como muitos Design Patterns são mais simples de implementar em uma linguagem dinâmica com os recursos do CLOS (herança múltipla, mixins, multimétodos, metaclasses, combinações de métodos, etc.). Várias extensões para Common Lisp para programação orientada a objetos foram propostas para serem incluídas no padrão ANSI Common Lisp, mas eventualmente CLOS foi adotado como o sistema de objetos padrão para Common Lisp. CLOS é um sistema de objetos dinâmicos com envio múltiplo e herança múltipla , e difere radicalmente dos recursos OOP encontrados em linguagens estáticas como C ++ ou Java . Como um sistema de objetos dinâmicos, o CLOS permite mudanças em tempo de execução para funções e classes genéricas. Métodos podem ser adicionados e removidos, classes podem ser adicionadas e redefinidas, objetos podem ser atualizados para mudanças de classe e a classe de objetos pode ser alterada.

CLOS foi integrado em ANSI Common Lisp. As funções genéricas podem ser usadas como funções normais e são um tipo de dados de primeira classe. Cada classe CLOS é integrada ao sistema de tipo Common Lisp. Muitos tipos de Lisp comuns têm uma classe correspondente. Há mais uso potencial do CLOS para Common Lisp. A especificação não diz se as condições são implementadas com CLOS. Os nomes de caminho e fluxos podem ser implementados com CLOS. Essas outras possibilidades de uso do CLOS para ANSI Common Lisp não fazem parte do padrão. As implementações reais do Common Lisp usam CLOS para nomes de caminho, fluxos, entrada-saída, condições, a implementação do próprio CLOS e muito mais.

Compilador e intérprete

Um interpretador Lisp executa diretamente o código-fonte Lisp fornecido como objetos Lisp (listas, símbolos, números, ...) lidos de expressões-s. Um compilador Lisp gera bytecode ou código de máquina a partir do código -fonte Lisp. O Common Lisp permite que funções individuais do Lisp sejam compiladas na memória e a compilação de arquivos inteiros em código compilado armazenado externamente ( arquivos fasl ).

Várias implementações de dialetos Lisp anteriores forneceram um interpretador e um compilador. Infelizmente, muitas vezes a semântica era diferente. Esses Lisps anteriores implementaram escopo léxico no compilador e escopo dinâmico no interpretador. O Common Lisp requer que tanto o interpretador quanto o compilador usem escopo léxico por padrão. O padrão Common Lisp descreve a semântica do interpretador e de um compilador. O compilador pode ser chamado usando a função compile para funções individuais e usando a função compile-file para arquivos. Common Lisp permite declarações de tipo e fornece maneiras de influenciar a política de geração de código do compilador. Para o último, várias qualidades de otimização podem receber valores entre 0 (não importante) e 3 (mais importante): velocidade , espaço , segurança , depuração e velocidade de compilação .

Há também uma função para avaliar o código Lisp: eval. evalaceita o código como expressões S pré-analisadas e não, como em algumas outras linguagens, como strings de texto. Desta forma, o código pode ser construído com as funções usuais do Lisp para a construção de listas e símbolos e, em seguida, esse código pode ser avaliado com a função eval. Várias implementações do Common Lisp (como Clozure CL e SBCL) estão implementando evalusando seu compilador. Desta forma, o código é compilado, embora seja avaliado usando a função eval.

O compilador de arquivo é invocado usando a função compile-file . O arquivo gerado com o código compilado é chamado de arquivo fasl (de carregamento rápido ). Esses arquivos fasl e também os arquivos de código-fonte podem ser carregados com a função load em um sistema Common Lisp em execução. Dependendo da implementação, o compilador de arquivo gera byte-code (por exemplo para a Java Virtual Machine ), código de linguagem C (que então é compilado com um compilador C) ou, diretamente, código nativo.

Implementações Common Lisp podem ser usadas interativamente, mesmo que o código seja totalmente compilado. A ideia de uma linguagem interpretada, portanto, não se aplica ao Common Lisp interativo.

A linguagem faz uma distinção entre tempo de leitura, tempo de compilação, tempo de carregamento e tempo de execução, e permite que o código do usuário também faça essa distinção para executar o tipo de processamento desejado na etapa desejada.

Alguns operadores especiais são fornecidos para se adequar especialmente ao desenvolvimento interativo; por exemplo, defvarsó atribuirá um valor à sua variável fornecida se ela ainda não estiver vinculada, enquanto defparametersempre executará a atribuição. Essa distinção é útil ao avaliar, compilar e carregar o código de forma interativa em uma imagem ao vivo.

Alguns recursos também são fornecidos para ajudar a escrever compiladores e interpretadores. Os símbolos consistem em objetos de primeiro nível e são diretamente manipuláveis ​​pelo código do usuário. O progvoperador especial permite criar ligações léxicas programaticamente, enquanto os pacotes também são manipuláveis. O compilador Lisp está disponível em tempo de execução para compilar arquivos ou funções individuais. Isso facilita o uso do Lisp como um compilador ou interpretador intermediário para outra linguagem.

Exemplos de código

Paradoxo do aniversário

O programa a seguir calcula o menor número de pessoas em uma sala para as quais a probabilidade de aniversários únicos é inferior a 50% (o paradoxo do aniversário , onde para 1 pessoa a probabilidade é obviamente 100%, para 2 é 364/365, etc. ) A resposta é 23.

Por convenção, constantes em Common Lisp são incluídas com caracteres +.

(defconstant +year-size+ 365)

(defun birthday-paradox (probability number-of-people)
  (let ((new-probability (* (/ (- +year-size+ number-of-people)
                               +year-size+)
                            probability)))
    (if (< new-probability 0.5)
        (1+ number-of-people)
        (birthday-paradox new-probability (1+ number-of-people)))))

Chamando a função de exemplo usando o REPL (Read Eval Print Loop):

CL-USER > (birthday-paradox 1.0 1)
23

Classificando uma lista de objetos pessoais

Definimos uma classe persone um método para exibir o nome e a idade de uma pessoa. Em seguida, definimos um grupo de pessoas como uma lista de personobjetos. Em seguida, iteramos sobre a lista classificada.

(defclass person ()
  ((name :initarg :name :accessor person-name)
   (age  :initarg :age  :accessor person-age))
  (:documentation "The class PERSON with slots NAME and AGE."))

(defmethod display ((object person) stream)
  "Displaying a PERSON object to an output stream."
  (with-slots (name age) object
    (format stream "~a (~a)" name age)))

(defparameter *group*
  (list (make-instance 'person :name "Bob"   :age 33)
        (make-instance 'person :name "Chris" :age 16)
        (make-instance 'person :name "Ash"   :age 23))
  "A list of PERSON objects.")

(dolist (person (sort (copy-list *group*)
                      #'>
                      :key #'person-age))
  (display person *standard-output*)
  (terpri))

Ele imprime os três nomes com idade decrescente.

Bob (33)
Ash (23)
Chris (16)

Exponenciando por quadratura

O uso da macro LOOP é demonstrado:

(defun power (x n)
  (loop with result = 1
        while (plusp n)
        when (oddp n) do (setf result (* result x))
        do (setf x (* x x)
                 n (truncate n 2))
        finally (return result)))

Exemplo de uso:

CL-USER > (power 2 200)
1606938044258990275541962092341162602522202993782792835301376

Compare com a exponenciação embutida:

CL-USER > (= (expt 2 200) (power 2 200))
T

Encontre a lista de shells disponíveis

WITH-OPEN-FILE é uma macro que abre um arquivo e fornece um fluxo. Quando o formulário está retornando, o arquivo é fechado automaticamente. FUNCALL chama um objeto de função. O LOOP coleta todas as linhas que correspondem ao predicado.

(defun list-matching-lines (file predicate)
  "Returns a list of lines in file, for which the predicate applied to
 the line returns T."
  (with-open-file (stream file)
    (loop for line = (read-line stream nil nil)
          while line
          when (funcall predicate line)
          collect it)))

A função AVAILABLE-SHELLS chama a função LIST-MATCHING-LINES acima com um nome de caminho e uma função anônima como predicado. O predicado retorna o nome do caminho de um shell ou NIL (se a string não for o nome do arquivo de um shell).

(defun available-shells (&optional (file #p"/etc/shells"))
  (list-matching-lines
   file
   (lambda (line)
     (and (plusp (length line))
          (char= (char line 0) #\/)
          (pathname
           (string-right-trim '(#\space #\tab) line))))))

Resultados de exemplo (no Mac OS X 10.6):

CL-USER > (available-shells)
(#P"/bin/bash" #P"/bin/csh" #P"/bin/ksh" #P"/bin/sh" #P"/bin/tcsh" #P"/bin/zsh")

Comparação com outros Lisps

Lisp comum é mais frequentemente comparado e contrastado com Scheme - se apenas porque eles são os dois dialetos Lisp mais populares. Scheme antecede CL e vem não apenas da mesma tradição Lisp, mas de alguns dos mesmos engenheiros - Guy L. Steele , com quem Gerald Jay Sussman projetou Scheme, presidiu o comitê de padrões para Common Lisp.

Common Lisp é uma linguagem de programação de propósito geral, em contraste com variantes do Lisp, como Emacs Lisp e AutoLISP, que são linguagens de extensão embutidas em produtos específicos (GNU Emacs e AutoCAD, respectivamente). Ao contrário de muitos Lisps anteriores, Common Lisp (como Scheme ) usa escopo de variável lexical por padrão para código interpretado e compilado.

A maioria dos sistemas Lisp cujos designs contribuíram para Common Lisp - como ZetaLisp e Franz Lisp - usaram variáveis ​​com escopo dinâmico em seus interpretadores e variáveis ​​com escopo léxico em seus compiladores. Scheme introduziu o uso exclusivo de variáveis ​​com escopo léxico para Lisp; uma inspiração do ALGOL 68 . CL também suporta variáveis ​​com escopo dinâmico, mas elas devem ser declaradas explicitamente como "especiais". Não há diferenças no escopo entre interpretadores ANSI CL e compiladores.

Common Lisp é algumas vezes denominado Lisp-2 e Scheme como Lisp-1 , referindo-se ao uso do CL de namespaces separados para funções e variáveis. (Na verdade, CL tem muitos namespaces, como aqueles para tags go, nomes de bloco e looppalavras - chave). Há uma controvérsia de longa data entre os defensores do CL e do Scheme sobre as compensações envolvidas em vários namespaces. No Scheme, é (amplamente) necessário evitar dar nomes de variáveis ​​que conflitem com as funções; As funções de esquema freqüentemente têm argumentos nomeados lis, lstou lystpara não entrar em conflito com a função do sistema list. No entanto, em CL, é necessário referir-se explicitamente ao namespace da função ao passar uma função como um argumento - o que também é uma ocorrência comum, como no sortexemplo acima.

CL também difere de Scheme no tratamento de valores booleanos. Scheme usa os valores especiais #t e #f para representar a verdade e a falsidade. CL segue a convenção Lisp mais antiga de usar os símbolos T e NIL, com NIL representando também a lista vazia. Em CL, qualquer valor não NIL é tratado como verdadeiro por condicionais, como if, enquanto no Esquema todos os valores não # f são tratados como verdade. Essas convenções permitem que alguns operadores em ambas as linguagens sirvam como predicados (respondendo a uma pergunta com valor booleano) e retornando um valor útil para cálculos adicionais, mas no Esquema o valor '() que é equivalente a NIL em Common Lisp é avaliado como verdadeiro em uma expressão booleana.

Por último, os documentos de padrões do Esquema requerem otimização de chamada final , o que o padrão CL não exige . A maioria das implementações de CL oferece otimização de chamada final, embora frequentemente apenas quando o programador usa uma diretiva de otimização. No entanto, o estilo de codificação comum CL não favorece o uso ubíquo de recursão que o estilo Esquema prefere-o que um programador Esquema expressaria com recursão de cauda, um usuário CL normalmente expressam com uma expressão iterativa em do, dolist, loop, ou (mais recentemente) com o iteratepacote.

Implementações

Veja as implementações de Category Common Lisp .

Common Lisp é definido por uma especificação (como Ada e C ) em vez de uma implementação (como Perl ). Existem muitas implementações e as áreas de detalhes padrão nas quais eles podem diferir validamente.

Além disso, as implementações tendem a vir com extensões, que fornecem funcionalidades não abordadas no padrão:

  • Nível superior interativo (REPL)
  • Coleta de lixo
  • Depurador, Stepper e Inspector
  • Estruturas de dados fracas (tabelas hash)
  • Sequências extensíveis
  • LOOP extensível
  • Acesso ao ambiente
  • Protocolo de meta-objeto CLOS
  • Streams extensíveis baseados em CLOS
  • Sistema de Condição baseado em CLOS
  • Streams de rede
  • CLOS persistente
  • Suporte Unicode
  • Interface de idioma estrangeiro (geralmente para C)
  • Interface do sistema operacional
  • Interface Java
  • Threads e multiprocessamento
  • Entrega de aplicativos (aplicativos, bibliotecas dinâmicas)
  • Salvando imagens

Bibliotecas de software livre e de código aberto foram criadas para suportar extensões para Common Lisp de uma forma portátil e são encontradas mais notavelmente nos repositórios dos projetos Common-Lisp.net e CLOCC (Common Lisp Open Code Collection).

As implementações Common Lisp podem usar qualquer combinação de compilação de código nativo, compilação de código de byte ou interpretação. Common Lisp foi projetado para suportar compiladores incrementais , compiladores de arquivo e compiladores de bloco. Declarações padrão para otimizar a compilação (como inlining de função ou especialização de tipo) são propostas na especificação da linguagem. As implementações mais comuns do Lisp compilam o código-fonte para o código de máquina nativo . Algumas implementações podem criar aplicativos autônomos (otimizados). Outros compilam para bytecode interpretado , que é menos eficiente do que o código nativo, mas facilita a portabilidade do código binário. Alguns compiladores compilam código Common Lisp para código C. O equívoco de que Lisp é uma linguagem puramente interpretada é mais provável porque os ambientes Lisp fornecem um prompt interativo e que o código é compilado um por um, de forma incremental. Com Common Lisp, a compilação incremental é amplamente utilizada.

Algumas implementações baseadas em Unix ( CLISP , SBCL ) podem ser usadas como linguagem de script ; isto é, invocado pelo sistema de forma transparente da maneira que um interpretador de shell Perl ou Unix é.

Lista de implementações

Implementações comerciais

Allegro Common Lisp
para Microsoft Windows, FreeBSD, Linux, Apple macOS e várias variantes do UNIX. Allegro CL fornece um Ambiente de Desenvolvimento Integrado (IDE) (para Windows e Linux) e recursos abrangentes para entrega de aplicativos.
Lisp comum líquido
anteriormente chamado de Lucid Common Lisp . Apenas manutenção, sem novos lançamentos.
LispWorks
para Microsoft Windows, FreeBSD, Linux, Apple macOS, iOS, Android e várias variantes do UNIX. LispWorks fornece um Ambiente de Desenvolvimento Integrado (IDE) (disponível para a maioria das plataformas, mas não para iOS e Android) e recursos abrangentes para entrega de aplicativos.
mocl
para iOS, Android e macOS.
Open Genera
para DEC Alpha.
Scieneer Common Lisp
que é projetado para computação científica de alto desempenho.

Implementações livremente redistribuíveis

Armed Bear Common Lisp (ABCL)
Uma implementação de CL que é executada na Java Virtual Machine . Inclui um compilador para código de bytes Java e permite acesso a bibliotecas Java a partir do CL. Anteriormente, era apenas um componente do Armed Bear J Editor .
CLISP
Uma implementação de compilação de bytecode, portátil e executada em vários sistemas Unix e semelhantes (incluindo macOS ), bem como Microsoft Windows e vários outros sistemas.
Clozure CL (CCL)
Originalmente um fork gratuito e de código aberto do Macintosh Common Lisp. Como essa história indica, o CCL foi escrito para Macintosh, mas o Clozure CL agora roda em macOS , FreeBSD , Linux , Solaris e Windows . Portas x86 de 32 e 64 bits são suportadas em cada plataforma. Além disso, existem portas Power PC para Mac OS e Linux. CCL era conhecido anteriormente como OpenMCL, mas esse nome não é mais usado, para evitar confusão com a versão de código aberto do Macintosh Common Lisp.
CMUCL
Originalmente da Carnegie Mellon University , agora mantido como software livre e de código aberto por um grupo de voluntários. CMUCL usa um compilador de código nativo rápido. Ele está disponível em Linux e BSD para Intel x86; Linux para Alpha; macOS para Intel x86 e PowerPC; e Solaris, IRIX e HP-UX em suas plataformas nativas.
Corman Common Lisp
para Microsoft Windows. Em janeiro de 2015, Corman Lisp foi publicado sob a licença do MIT.
Lisp comum incorporável (ECL)
ECL inclui um interpretador e compilador de bytecode. Ele também pode compilar código Lisp para código de máquina por meio de um compilador C. ECL então compila o código Lisp para C, compila o código C com um compilador C e pode então carregar o código de máquina resultante. Também é possível ECL incorporar em C programas e código C em programas Lisp Comum.
GNU Common Lisp (GCL)
O compilador Lisp do Projeto GNU . Ainda não totalmente compatível com ANSI, GCL é, no entanto, a implementação preferida para vários grandes projetos, incluindo as ferramentas matemáticas Maxima , AXIOM e (historicamente) ACL2 . O GCL é executado no Linux em onze arquiteturas diferentes e também no Windows, Solaris e FreeBSD .
Macintosh Common Lisp (MCL)
A versão 5.2 para computadores Apple Macintosh com um processador PowerPC executando Mac OS X é de código aberto. RMCL (baseado em MCL 5.2) é executado em computadores Apple Macintosh baseados em Intel usando o conversor binário Rosetta da Apple.
ManKai Common Lisp (MKCL)
Uma filial da ECL . O MKCL enfatiza a confiabilidade, estabilidade e qualidade geral do código por meio de um sistema de tempo de execução altamente retrabalhado e multi-threaded nativo. No Linux, o MKCL apresenta um sistema de tempo de execução totalmente compatível com POSIX.
Movitz
Implementa um ambiente Lisp para computadores x86 sem depender de nenhum sistema operacional subjacente.
Poplog
Poplog implementa uma versão do CL, com POP-11 , e opcionalmente Prolog e Standard ML (SML), permitindo a programação em linguagem mista. Para todos, a linguagem de implementação é POP-11, que é compilado de forma incremental. Ele também possui um editor semelhante ao Emacs integrado que se comunica com o compilador.
Steel Bank Common Lisp (SBCL)
Uma filial da CMUCL . "Em termos gerais, o SBCL se distingue do CMU CL por uma maior ênfase na capacidade de manutenção." SBCL roda nas plataformas CMUCL, exceto HP / UX; além disso, ele roda em Linux para AMD64, PowerPC, SPARC, MIPS, Windows x86 e tem suporte experimental para rodar em Windows AMD64. SBCL não usa um intérprete por padrão; todas as expressões são compiladas em código nativo, a menos que o usuário ative o interpretador. O compilador SBCL gera código nativo rápido de acordo com uma versão anterior do The Computer Language Benchmarks Game .
Ufasoft Common Lisp
porta do CLISP para a plataforma Windows com núcleo escrito em C ++.

Outras implementações

Austin Kyoto Common Lisp
uma evolução do Kyoto Common Lisp por Bill Schelter
Butterfly Common Lisp
uma implementação escrita em Scheme para o computador multiprocessador BBN Butterfly
CLICC
um compilador Common Lisp para C
CLOE
Lisp comum para PCs da Symbolics
Codemist Common Lisp
usado para a versão comercial do sistema de álgebra computacional Axiom
ExperCommon Lisp
uma implementação inicial para o Apple Macintosh por ExperTelligence
Golden Common Lisp
uma implementação para PC pela GoldHill Inc.
Ibuki Common Lisp
uma versão comercializada do Kyoto Common Lisp
Kyoto Common Lisp
o primeiro compilador Common Lisp que usou C como linguagem de destino. GCL, ECL e MKCL originam-se dessa implementação do Common Lisp.
eu
uma pequena versão do Common Lisp para sistemas embarcados desenvolvido pela IS Robotics, agora iRobot
Máquinas Lisp (da Symbolics , TI e Xerox)
forneceu implementações de Common Lisp além de seu dialeto Lisp nativo (Lisp Machine Lisp ou Interlisp). CLOS também estava disponível. O Symbolics fornece uma versão aprimorada do Common Lisp.
Procyon Common Lisp
uma implementação para Windows e Mac OS, usada por Franz para sua porta do Windows do Allegro CL
Star Sapphire Common LISP
uma implementação para o PC
SubL
uma variante do Common Lisp usado para a implementação do sistema baseado em conhecimento Cyc
Lisp comum de nível superior
uma implementação inicial para execução simultânea
WCL
uma implementação de biblioteca compartilhada
VAX Common Lisp
Implementação da Digital Equipment Corporation que foi executada em sistemas VAX executando VMS ou ULTRIX
XLISP
uma implementação escrita por David Betz

Formulários

Common Lisp é usado para desenvolver aplicativos de pesquisa (geralmente em Inteligência Artificial ), para desenvolvimento rápido de protótipos ou para aplicativos implantados.

Common Lisp é usado em muitos aplicativos comerciais, incluindo o Yahoo! Site de comércio na web da loja, que originalmente envolvia Paul Graham e mais tarde foi reescrito em C ++ e Perl . Outros exemplos notáveis ​​incluem:

Também existem aplicativos de código aberto escritos em Common Lisp, como:

Veja também

Referências

Bibliografia

Uma lista cronológica de livros publicados (ou em vias de publicação) sobre Common Lisp (a linguagem) ou sobre programação com Common Lisp (especialmente programação AI).

links externos