ATENÇÃO: ESTE LIVRO ESTÁ ALTAMENTE INCOMPLETO! O objetivo é prover uma tradução apropriada para o Common Lisp Cookbook original, em concordância com o licenciamento e com o texto do mesmo. Visite o repositório no GitHub e nos ajude a traduzi-lo.

ATENÇÃO: CAPÍTULOS AINDA ESTÃO FALTANDO. Caso queira consultar informações faltantes, você pode achar o Cookbook original neste link.

The Common Lisp Cookbook (PT-BR)

Cookbook, subst.
um livro contendo receitas e outras informações sobre a preparo e cozimento de comida.

agora com Lisp extra (e com cobertura de Português do Brasil!)

Informações

Este é um projeto colaborativo focado em prover, para Common Lisp, algo similar ao Perl Cookbook, publicado pela O'Reilly. Mais detalhes sobre o que este livro é e o que ele não é podem ser encontrados neste tópico encontrado em comp.lang.lisp.

Se você quer contribuir com o CL Cookbook, por favor, envie um pull request ou preencha uma issue no GitHub!

Sim, estamos falando com você! Precisamos de mais contribuidores - escreva um capítulo que está faltando e adicione-o, encontre uma pergunta em aberto e dê uma resposta, encontre bugs e reporte-os (se você não tem ideia do que falta mas gostaria de ajudar, dê uma olhada no índice do Perl Cookbook). Não se preocupe com a formatação, apenas envie texto simples se você quiser - cuidaremos disso depois.

Desde já, obrigado pela sua ajuda!

Notas dos tradutores

Este livro é uma tradução direta, para o Português brasileiro, do Common Lisp Cookbook, pela comunidade Common Lisp Brasil.

Caso haja erros de tradução ou de Português nestas páginas, você está livre para contribuir no mesmo formato provido acima, ou poderá abrir uma issue no repositório desta tradução.

Nosso foco primário é na pura tradução do Cookbook original, portanto, se você está interessado em contribuir com conteúdo novo, por favor, dirija-se ao repositório original.

Contribuidores

Finalmente, o crédito por finalmente dar a luz ao projeto provavelmente vai para "dj_special_ed", que postou esta mensagem em comp.lang.lisp.

Tradutores

Outros recursos

Licença

Redistribution and use of the "Common Lisp Cookbook" in its orginal form (HTML) or in 'derived' forms (PDF, Postscript, RTF and so forth) with or without modification, are permitted provided that the following condition is met:

  • Redistributions must reproduce the above copyright notice, this and the following disclaimer in the document itself and/or other materials provided with the distribution.

IMPORTANT: This document is provided by the Common Lisp Cookbook Project "as is" and any expressed or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the Common Lisp Cookbook Project be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this documentation, even if advised of the possibility of such damage.

LispCookbook GithubGroup addendum: this document is now managed in a modified format.

Copyright:
2015-2017 LispCookbook Github Group
2002-2007 The Common Lisp Cookbook Project.

Começando

Passos simples para instalar um ambiente de desenvolvimento e iniciar um projeto.

Quer uma instalação em dois-cliques? Então veja o Portacle, um ambiente portátil e multiplataforma de Common Lisp. Ele inclui Emacs25, SBCL (a implementação), Quicklisp (gerenciador de pacotes), Slime (a IDE) e Git. É a forma mais rápida de começar!

Instalando uma implementação

com seu gerenciador de pacotes

TL;DR:

apt-get install sbcl

Common Lisp foi padronizado através de um documento ANSI, então ele pode ser implementado de diversas formas; veja a lista de implementações da Wikipédia.

As implementações a seguir são empacotadas para o Debian e provavelmente também para a sua distro:

Em dúvida, apenas use o SBCL.

Veja também:

e este pacote Debian para Clozure CL.

com Roswell

Roswell é:

  • um gerenciador de implementações: torna mais fácil instalar uma implementação de Common Lisp (ros install ecl), uma exata versão de uma implementação (ros install sbcl/1.2.0), e mudar para uma implementação padrão (ros use ecl);
  • um ambiente de scripting (ajuda a executar Lisp através do shell, obter argumentos de linha de comando, ...);
  • um instalador de scripts;
  • um ambiente para testes (para executar testes, incluindo em plataformas populares de Integração Contínua);
  • uma ferramenta de compilação (para compilar imagens e executáveis de forma portátil).

Você encontrará diversas formas de instalação na Wiki do Roswell (pacote Debian, instalador Windows, Brew/Linux Brew, etc).

com Docker

Se você já conhece o Docker, você pode começar a usar Common Lisp rapidamente. A imagem daewok/lisp-devel-docker inclui as versões recentes de SBCL, CCL, ECL e ABCL, além de Quicklisp instalado na pasta home (/home/lisp) para que possamos executar ql:quickload logo de cara.

Funciona em GNU/Linux, Mac e Windows.

O comando a seguir baixará a imagem requerida (mais ou menos 400MB), colocará seus arquivos de código locais dentro da imagem Docker, onde indicado, e mostrará o REPL do SBCL:

docker run --rm -it -v /path/to/local/code:/usr/local/share/common-lisp/source daewok/lisp-devel:base sbcl`

Mas nos ainda queremos desenvolver usando Emacs e Slime, então precisamos conectar o Slime ao Lisp dentro do Docker. Veja slime-docker para uma biblioteca que o ajudará a configurar isto.

Iniciando um REPL

Apenas abra o executável da implementação na linha de comando para entrar no REPL (Read Eval Print Loop, ou Laço de Leitura-Interpretação-Escrita).

Saia com (quit) ou ctrl-d (em algumas implementações).

Eis um exemplo de uma sessão:

user@debian:~$ sbcl
This is SBCL 1.3.14.debian, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.

SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses.  See the CREDITS and COPYING files in the
distribution for more information.
* (+ 1 2)

3
* (quit)
user@debian:~$

Você pode melhorar um pouco o REPL (as teclas de setas não funcionam, ele não tem um histórico de comandos, ....) com o programa rlwrap:

apt-get install rlwrap

E então:

rlwrap sbcl

Mas nós configuraremos nosso editor para oferecer uma melhor experiência, ao invés de trabalhar diretamente neste REPL. Veja Suporte de Editores de Texto.

TODO: subsituir o link acima.

Libraries (Bibliotecas)

Common Lisp tem centenas de libraries disponíveis sob uma licença livre de software. Veja:

  • Quickdocs - a biblioteca de documentação para CL.
  • A lista Awesome-cl, uma lista curada de librariess.
  • Cliki, a wiki de Common Lisp.

Terminologia

  • No mundo de Common Lisp, um package (pacote) é uma forma de agrupar símbolos e prover encapsulamento. É similar a um namespace de C++, um module (módulo) de Python ou a um package de Java.

  • Um system (sistema) é uma coleção de códigos-fonte de CL, agrupados com um arquivo .asd que informa como compilá-los e carregá-los. Às vezes, há um relacionamento próximo entre systems e packages, mas isto não é algo obrigatório. Um system pode declarar uma dependência por outro system. Systems são gerenciados pelo ASDF (Another System Definition Facility), que oferece funcionalidades similares ao make e ao ld.so, e se tornou um padrão.

  • Uma library (biblioteca) ou um project (projeto) de Common Lisp normalmente consiste de um ou vários systems ASDF (e é distribuído como um project Quicklisp).

Instalando o Quicklisp

Quicklisp é mais que um gerenciador de pacotes, ele também é um repositório central (um dist) que assegura que todas as bibliotecas compilem juntas.

Ele providencia seu próprio dist, mas também é possível criar o seu próprio.

Para instalá-lo, nós podemos:

1 - Executar o seguinte comando, em qualquer lugar:

curl -O https://beta.quicklisp.org/quicklisp.lisp

e entrar em um REPL Lisp e carregar o arquivo baixado:

sbcl --load quicklisp.lisp

Ou:

2 - Instalar o pacote Debian:

apt-get install cl-quicklisp

e carregá-lo, de um REPL:

(load "/usr/share/cl-quicklisp/quicklisp.lisp")

E então, em ambos os casos, ainda através do REPL:

(quicklisp-quickstart:install)

Isto criará o diretório ~/quicklisp/, onde Quicklisp manterá seu estado e seus projetos baixados.

Se você quer que o Quicklisp seja sempre carregado em suas sessões Lisp, execute (ql:add-to-init-file): isto adicionará os comandos certos ao arquivo de inicialização da sua implementação de CL. Do contrário, você deverá executar (load "~/quicklisp/setup.lisp") em cada sessão, se você quiser utilizar o Quicklisp ou uma das bibliotecas instaladas através do mesmo.

Este comando adiciona o seguinte em (por exemplo) seu arquivo ~/.sbclrc:

#-quicklisp
  (let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp"
                                         (user-homedir-pathname))))
    (when (probe-file quicklisp-init)
      (load quicklisp-init)))

Instalando libraries

No REPL:

(ql:quickload "nome-do-package")

e voilà. Veja a documentação do Quicklisp para mais comandos.

Note, também, que dezenas de libraries Common Lisp estão empacotadas como pacotes Debian. O nome dos pacotes normalmente começam com o prefixo cl- (use apt-cache search --names-only "^cl-.*" para listar todos eles).

Por exemplo, para utilizar a library CL-PPCRE (para expressões regulares), deve-se, primeiramente, instalar o pacote cl-ppcre.

Então, no SBCL ou no ECL, este pode ser utilizado com:

(require "asdf")
(require "cl-ppcre")
(cl-ppcre:regex-replace "fo+" "foo bar" "frob")

Veja mais: https://wiki.debian.org/CommonLisp

Gerenciamento avançado de dependências

Perceba que estas informações não são necessárias para começar.

Quicklisp instala as libraries no diretório ~/quicklisp/local-projects/. Uma library ali instalada estará automaticamente disponível para qualquer projeto.

Fornecendo sua própria versão de uma biblioteca: Clonando projects

Dada a propriedade acima, podemos clonar qualquer library para o diretório local-projects e ele será encontrado pelo Quicklisp e disponível logo em seguida:

(ql:quickload "package")

Como trabalhar com versões locais de libraries

Se precisarmos de que bibliotecas sejam instaladas localmente, para apenas um projeto, ou para facilmente embarcar uma lista de dependências com uma aplicação, podemos utilizar o Qlot.

Quicklisp também fornece bundles Quicklisp. São conjuntos independentes de systems que são exportados do Quicklisp e carregáveis sem envolvê-lo.

Por fim, há também o Quicklisp controller, para nos ajudar a construir dists.

Trabalhando com Projects

Agora que temos Quicklisp e nosso editor prontos, podemos começar a escrever código Lisp em um arquivo e interagir com o REPL.

Mas, e se quisermos trabalhar com um projeto existente, ou criar um novo? Como procedemos, qual a sequência correta de defpackages, o que colocar em um arquivo .asd, e como carregar um projeto no REPL?

Criando um novo project

Alguns criadores de projects ajudam a criar a estrutura do projeto. Gostamos de cl-project, que também configura um esqueleto para testes.

Em resumo:

(ql:quickload "cl-project")
(cl-project:make-project #P"./caminho/para/raiz/do/projeto/")

Isto criará uma estrutura de diretório como essa:

|-- my-project.asd
|-- my-project-test.asd
|-- README.markdown
|-- README.org
|-- src
|   `-- my-project.lisp
`-- tests
    `-- my-project.lisp

Onde my-project.asd lembra o seguinte:

(defsystem "my-project"
  :version "0.1.0"
  :author ""
  :license ""
  :depends-on ()  ;; <== list of Quicklisp dependencies
  :components ((:module "src"
                :components
                ((:file "my-project"))))
  :description ""
  :long-description
  #.(read-file-string
     (subpathname *load-pathname* "README.markdown"))
  :in-order-to ((test-op (test-op "my-project-test"))))

E src/my-project.lisp lembra isto:

(defpackage footest
  (:use :cl))
(in-package :footest)

Como carregar um project existente

Você criou seu novo project, ou tem um project existente, e você gostaria de trabalhar com ele no REPL, mas o Quicklisp não o conhece. O que fazer?

Primeiramente, se você criá-lo ou cloná-lo em ~/quicklisp/local-projects, você poderá (ql:quickload ...) seu project, sem mais ressalvas.

Usualmente, você vai querer "entrar" no system, através do REPL, neste estágio:

(use-package :my-project)

Por fim, você poderá compilar ou interpretar os códigos-fonte (my-project.lisp) com C-c C-k ou C-c C-c1 (slime-compile-defun) em um form, e ver seu resultado no REPL.

Outra solução é usar a lista de projects conhecidos do ASDF:

(pushnew "~/caminho/para/raiz/do/projeto/" asdf:*central-registry* :test #'equal)

E, já que o ASDF é integrado ao Quicklisp, nós podemos dar quickload no project.

Feliz hacking!

1 N. do T.: Estes atalhos dizem respeito ao Emacs ou ao Portacle. Não se preocupe por ainda não compreendê-los.

Mais configurações

Você pode querer definir o formato padrão de codificação do SBCL para UTF-8:

(setf sb-impl::*default-external-format* :utf-8)

Você pode adicionar isso ao seu ~/sbclrc.

Leia Mais

Créditos

Suporte de Editores

Nosso editor preferido para o desenvolvimento em Common Lisp ainda é o Emacs, mas ele não é a única opção.

Emacs

SLIME é o Superior Lisp Interaction Mode for Emacs. Ele possui suporte para interação com um processo de Common Lisp rodando, para compilação, debugging, documentação, etc. Funciona com diferentes implementações.

Portacle é um ambiente Common Lisp multiplatforma e portável (não é preciso instalar binários na máquina). Vem com Emacs25, SBCL, Quicklisp, Slime e Git.

Instalando Slime

Slime está no repositório oficial para pacotes Emacs Lisp, GNU ELPA (do Emacs24 em diante). Instale-o com:

  M-x package-install RET slime RET

Agora você pode rodar Slime com M-x slime.

Veja também:

  • http://wikemacs.org/wiki/SLIME - exemplos de configuração e extensões (em inglês).

Usando Emacs como uma IDE.

TODO: WTF? SEPARARAM PQ? Veja "Usando Emacs como uma IDE".

Configurando Emacs no Windows ou Mac

Veja "Configurando Emacs no Windows ou Mac".

Vim

Slimv é um ambiente Common Lisp completo dentro do Vim.

Vlime um ambiente Common Lisp completo para Vim (e Neovim), similar ao SLIME para Emacs e ao SLIMV.

Lem

Lem é um editor feito para desenvolvimento em Common Lisp. A partir do momento que é instalado você pode começar a desenvolver. Sua interfece lembra a do Emacs e SLIME. Vem com uma interface em ncurses, bem como um frontend em Electron.

Atom

Atom-Slime. Esse pacote permite o desenvolvimento interativo de codigo Common Lisp, transformando o Atom em uma completa IDE para Lisp.

Sublime Text

Sublime Text suporta um REPL Lisp e eval de código.

Você precisa instalar o pacote "SublimeREPL" e então escolher a implementação de CL em Tools/SublimeREPL, e então eval o que você quiser.

Notebooks

cl-jupyter é um kernel Common Lisp para notebooks Jupyter.

Darkmatter é um ambiente CL parecido com notebooks.

REPLs

cl-repl é um REPL ipython-like. Ele suporta symbol completion, comandos shell e magic, editar comandos em um arquivo e um debugger simples.

Outros

Para reviews de plugins para outros editores, incluindo versões free de editores proprietários (como Allegro e Lispworks), veja Articulate Common Lisp.

Estruturas de Dados

Nós esperamos ter aqui uma referência para estruturas de dados comuns. Para apropriadamente aprender a linguagem procure ler outras fontes. Os seguintes links também podem conter mais detalhes (em inglês):

  • Practical CL, by Peter Seibel
  • CL Recipes, de E. Weitz, com várias dicas e explicações,
  • CL standard com um bom TOC, referência de funções, extensivas descrições, mais exemplos e with a nice TOC, functions reference, extensive descriptions, more examples and warnings (i.e: everything).
  • a Common Lisp quick reference

Nåo deixe de ler o apêndice e se precisar de mais estruturas de dados veja a lista awesome-cl e também Quickdocs.

Listas

Montando listas, Cons cells e listas

Uma lista também é uma sequência, portanto é possível usar as funções abaixo.

Cons cells são os elementos básicos de uma lista. Listas são construídas agrupando-se cons cells.

(cons 1 2)
;; => (1 . 2) ;; representação com um ponto, um dotted pair.

Assim:

[o|o]--- 2
 |
 1

Se o cdr da primeira cell for outra cons cell, e se o cdr desta última cons cell for nil, uma lista é montada:

(cons 1 (cons 2 nil))
;; => (1 2)

Mais ou menos assim:

[o|o]---[o|/]
 |       |
 1       2

(ascii art criada com draw-cons-tree).

Percebeu como a lista não foi representada como um dotted pair? O printer entende a convenção.

Por fim, é possível construir uma lista literal usando list:

(list 1 2)
;; => (1 2)

ou usando uma aspa simples:

'(1 2)
;; => (1 2)

que nada mais é do que uma simplificação para a chamada de função (quote (1 2)).

Listas Circulares

O car ou o cdr de uma cons cell pode referenciar outros objetos, incluindo a si mesmo ou a outras cells da mesma lista. Portanto car e cdr podem ser usados para definir estruturas de auto-referência, tais como listas circulares.

Antes de começar a trabalhar com listas circulares, avise ao printer, para que ele as reconheça e que não tente imprimir a lista completa setando *print-circle* para T`:

(setf *print-circle* t)

Uma função que modifica uma lista, de forma que o último cdr aponte para o começo da lista é:

(defun circular! (items)
  "Modifica o último cdr de uma lista ITEMS, retornando uma lista circular"
  (setf (cdr (last items)) items))
(circular! (list 1 2 3))
;; => #1=(1 2 3 . #1#)

(fifth (circular! (list 1 2 3)))
;; => 2

A função list-length reconhece uma lista circular, retornando nil.

O reader também pode criar uma lista circular, usando a notação Sharpsign Equal-Sign Um objeto, (como uma lista) pode ser prefixado com #n= onde n é um número inteiro decimal sem sinal (um ou mais dígitos). O label #n# pode ser depois ser usado para referenciar o objeto na expressão:

'#42=(1 2 3 . #42#)
;; => #1=(1 2 3 . #1#)

Note que o label dado ao reader (n=42) é descartado após a leitura, e que o printer define um novo label (n=1).

Leitura complementar

car/cdr ou primeiro/resto (e segundo... ao décimo)

(car (cons 1 2)) ;; => 1
(cdr (cons 1 2)) ;; => 2
(first (cons 1 2)) ;; => 1
(first '(1 2 3)) ;; => 1
(rest '(1 2 3)) ;; => (2 3)

É possível atribuir qualquer novo valor usando setf

last, butlast, nbutlast (&optional n)

retorna a última cons cell em uma lista (ou a n-ésima última cons cell).

(last '(1 2 3))
;; => (3)
(car (last '(1 2 3)) ) ;; ou (first (last …))
;; => 3
(butlast '(1 2 3))
;; => (1 2)

Em Alexandria, lastcar é equivalente a (first (last …)):

(alexandria:lastcar '(1 2 3))
;; => 3

reverse, nreverse

reverse e nreverse retorna uma nova sequência.

nreverse é destrutiva. O N significa non-consing, ou seja, não é necessário alocar novas cons cells. Ela pode (e na prática, é isso que ela faz) reusar e modificar a sequência original:

(defparameter mylist '(1 2 3))
;; => (1 2 3)
(reverse mylist)
;; => (3 2 1)
mylist
;; => (1 2 3)
(nreverse mylist)
;; => (3 2 1)
mylist
;; => (1) in SBCL but implementation dependant.

append

append recebe qualquer quantidade de listas como argumento e retorna uma nova lista, contendo os elementos de todos os seus arguments:

(append (list 1 2) (list 3 4))
;; => (1 2 3 4)

A nova lista compartilha de algumas cons cells com a (3 4):

http://gigamonkeys.com/book/figures/after-append.png

Nota: append em cl21 é genérica (para listas, strings, vetores e suas abstract-sequence)

nconc é a equivalente reciclável

push (item, place)

push insere item à lista armazenada em place, guarda a lista resultante em place, e retorna a lista.

(defparameter mylist '(1 2 3))
(push 0 mylist)
;; => (0 1 2 3)
(defparameter x ’(a (b c) d))
;; => (A (B C) D)
(push 5 (cadr x))
;; => (5 B C)
x
;; => (A (5 B C) D)

push é equivalente a (setf place (cons item place)), porém, as subforms de place sofrem eval apenas uma vez, e item tem o eval antes de place.

Não existe uma função nativa para adicionar ao fim de uma lista. Esta é uma operação mais custosa (é preciso percorrer toda a lista). Então, se você precisa realizar essa operação faça o seguinte: Considere utilizar outra esrutura de dados, ou ponha sua lista ao contrário quando necessário.

pop

uma operação destrutiva.

nthcdr (index, list)

Use-a se first, second e todas as outras até tenth não forem o suficiente.

car/cdr e composites (cadr, caadr...) - acessando listas dentro de listas

Essas fazem sentido quando aplicadas a listas que contém outras listas.

(caar (list 1 2 3))                  ==> error
(caar (list (list 1 2) 3))           ==> 1
(cadr (list (list 1 2) (list 3 4)))  ==> (3 4)
(caadr (list (list 1 2) (list 3 4))) ==> 3

destructuring-bind (parameters*, list)

Liga os valores do parâmetro à lista de elementos. É possível desestruturar árvores, plists, e até mesmo prover defaults.

Match simples:

(destructuring-bind (x y z) (list 1 2 3)
  (list :x x :y y :z z))
;; => (:X 1 :Y 2 :Z 3)

Match dentro de sublistas:

(destructuring-bind (x (y1 y2) z) (list 1 (list 2 20) 3)
  (list :x x :y1 y1 :y2 y2 :z z))
;; => (:X 1 :Y1 2 :Y2 20 :Z 3)

A lista de parâmetros pode fazer uso dos parâmetros &optional, &rest e &key.

(destructuring-bind (x (y1 &optional y2) z) (list 1 (list 2) 3)
  (list :x x :y1 y1 :y2 y2 :z z))
;; => (:X 1 :Y1 2 :Y2 NIL :Z 3)
(destructuring-bind (&key x y z) (list :z 1 :y 2 :x 3)
  (list :x x :y y :z z))
;; => (:X 3 :Y 2 :Z 1)

O parâmetro &whole está ligado à lista inteira. Ele deve vir à frente e pode ser prosseguido por outros parâmetros.

(destructuring-bind (&whole whole-list &key x y z) (list :z 1 :y 2 :x 3)
  (list :x x :y y :z z :whole whole-list))
;; => (:X 3 :Y 2 :Z 1 :WHOLE-LIST (:Z 1 :Y 2 :X 3))

Desestruturando uma plist, provendo defaults:

(exemplo retirado de Common Lisp Recipes, de E. Weitz, Apress, 2016)

(destructuring-bind (&key a (b :not-found) c
                     &allow-other-keys)
    ’(:c 23 :d "D" :a #\A :foo :whatever)
  (list a b c))
;; => (#\A :NOT-FOUND 23)

** TODO If this gives you the will to do pattern matching, see pattern matching.

Predicados: null, listp

null é equivalente a not, mas é o mais recomendado.

listp veerifica se um objeto é uma cons cell ou nil.

e predicados de sequência.

idiff, tailp, list*, make-list, fill, revappend, nreconc, consp, atom

(make-list 3 :initial-element "ta")
;; => ("ta" "ta" "ta")
(make-list 3)
;; => (NIL NIL NIL)
(fill * "hello")
;; => ("hello" "hello" "hello")

Sequências

lists e vectors (e, portanto strings) são sequências.

Note: veja também a página sobre strings.

Muitas das funções de sequência utilizam palavras-chave como argumentos. Todos os argumentos podem ser são opcionais, e se especificados, podem aparecer em qualquer ordem.

Atente-se ao argumento :teste. O seu padrão é eql (para strings use :equal).

Para o argumento :key deve-se passar nil, ou uma função com um argumento. Essa função key é usada como umo filtro pelo qual os elementos da sequência são visualizados. Por exemplo:

(find x y :key 'car)

é similar a (assoc* x y), ela busca por um elemento na lista em que o car seja igual a x, oa invés de um elemento que seja igual a x. Se :key for omitido ou nil, o filtro será efetivamente a funçãp identidade.

Exemplo com uma alist:

(defparameter my-alist (list (cons 'foo "foo")
                             (cons 'bar "bar")))
;; => ((FOO . "foo") (BAR . "bar"))
(find 'bar my-alist)
;; => NIL
(find 'bar my-alist :key 'car)
;; => (BAR . "bar")

Para algo mais, use um lambda que receba um parâmetro.

(find 'bar my-alist :key (lambda (it) (car it)))

Nota: cl21 possui short lambdas:

(find 'bar my-alist :key ^(car %))
(find 'bar my-alist :key (lm (it) (car it)))

Predicados: every, some,...

every, notevery (teste, sequência): retornam nil ou t, respectivamente, no momento em que um teste ou qualquer conjunto de elementos correspondentes a elementos de sequências retorne nil.

(defparameter foo '(1 2 3))
(every #'evenp foo)
;; => NIL
(some #'evenp foo)
;; => T

com uma lista de strings:

(defparameter str '("foo" "bar" "team"))
(every #'stringp str)
;; => T
(some #'(lambda (it) (= 3 (length it))) str)
;; => T
(some ^(= 3 (length %)) str) ;; in CL21
;; => T

some, notany (teste, sequência): retorna o valor do teste ou nil. mismatch (sequence-a, sequence-b): Retorna a posição em sequênce-a onde sequence-a sequence-b deixam de ser iguais. Retorna nil se as duas sequências forem iguais. Outros parâmetros: :from-end bool, :start1, :end[1,2].

Functions

Veja também as funções de sequência definidas em (em inglês) Alexandria: starts-with, ends-with, ends-with-subseq, length=, emptyp,…

length (sequence)

member (elt, sequence)

Retorna o tail de sequence a partir do primeiro elemento que satisfaça eql. Aceita :test, :test-not, :key(funções ou símbolos).

(member 2 '(1 2 3))
;; (2 3)

elt (sequence, index) - encontra a partir de index

Cuidado, neste caso a sequência vem primeiro.

count (foo sequence)

Retorna o número de elementos em sequence que dão match em foo.

Parâmetros adicionais: :from-end, :start, :end.

Veja também count-if, count-not (test-function sequence).

subseq (sequence start, [end])

É passível de "setf", mas funciona apenas se a nova sequência tiver o mesmo tamanho que a sequência a ser substituída.

sort, stable-sort (sequence, test [, key function])

Esta função é destrutiva, então você é de bom tom copiar a sequência antes da ordenação:

(sort (copy-seq seq) :test #'string<)

find, position (foo, sequence) - obter index

além de find-if, find-if-not, position-if, position-if-not (teste sequência). Veja também os parâmetros :key, :test.

(find 20 '(10 20 30))
;; 20
(position 20 '(10 20 30))
;; 1

search (sequence-a, sequence-b)

Procura por uma subsequência em sequence-b que correspondente a sequence-a. Retorna a posição da subsequência correspondente ou NIL. Possui os parâmetros from-end, end1/2 entre outros.

substitute, nsubstitute[if,if-not]

sort, stable-sort, merge

replace (sequence-a, sequence-b)

Substitui os elmentos de sequence-a pelos elementos de sequence-b.

remove, delete (foo sequence)

Faz uma cópia de sequence sem elementos que dêem match em foo. Possui os parâmetros :start/end,:keye:count`.

delete faz o mesmo, pórem, dependendo do contexto ela pode destruir a sequência original no processo.

(remove "foo" '("foo" "bar" "foo") :test 'equal)
;; => ("bar")

Veja também remove-if[-not] a seguir.

mapping (map, mapcar, remove-if[-not],...)

Se você já está familiarizado com map e filter em outras linguagens, provavelmete o que você procura é mapcar. Mas ele funciona apenas em listas, então para iterar em vetores e produzir tanto um vetor quanto uma lista, use (map 'list function vector).

mapcar também aceita múltiplas listas com &rest more-seqs. O mapeamento termina no momento em que a menor sequência chega ao fim.

Nota: map do cl21 é um mapcar para listas e vetores.

map recebe o tipo de saída desejado como o primeiro argumento ('list, 'vector, ou 'string):

(defparameter foo '(1 2 3))
(map 'list (lambda (it) (* 10 it)) foo)

Filter, aqui, é chamado de remove-if-not.

Flatten a list (Alexandria)

Com Alexandria, temor a função flatten.

Criando listas com variáveis

Este é um dos usos de backquote:

(defparameter *var* "bar")
;; First try:
'("foo" *var* "baz") ;; sem backquote
;; => ("foo" *VAR* "baz") ;; errado

Segunda tentaiva, usando backquote interpolation.

`("foo" ,*var* "baz")     ;; com backquote, vírgula
;; => ("foo" "bar" "baz") ;; correto

O backquote avisa que nós vamos fazer a interpolação e a vírgula repassa o valor da variável.

Se a nossa variável é uma lista:

(defparameter *var* '("bar" "baz"))
;; First try:
`("foo" ,*var*)
;; => ("foo" ("bar" "baz")) ;; uma nested list
`("foo" ,@*var*)            ;; backquote, comma-@ to
;; => ("foo" "bar" "baz")

E. Weitz avisa que "objetos gerados desta forma muito provavelmete da mesma estrutura (ver Receita 2-7)".

Comparando listas

É possível usar funções de set.

Set

interseção de listas

Quais elementos estão tanto em list-a quanto em list-b ?

(defparameter list-a '(0 1 2 3))
(defparameter list-b '(0 2 4))
(intersection list-a list-b)
;; => (2 0)

Remover os elementos de list-b de uma list-a

set-difference

(set-difference list-a list-b)
;; => (3 1)
(set-difference list-b list-a)
;; => (4)

Juntar duas listas

union

(union list-a list-b)
;; => (3 1 0 2 4) ;; a ordem pode ser diferente no seu Lisp

Remover elementos que estão em ambas as listas

set-exclusive-or

(set-exclusive-or list-a list-b)
;; => (4 3 1)

e sua forma "reciclável" (nintersection,...).

Veja também as funções setp, set-equal,... em Alexandria.

Fset - estrutura de dados imutáveis

Você também pode conferir a biblioteca FSet (em Quicklisp).

Arrays e Vectors

Arrays possuem características de tempo de acesso constante.

Podem ser fixos ou ajustáveis. Um simple array não pode ser separado (usando :displaced-to, para apontar para outro array), ajustável (:adjust-array), e também não possui um fill pointer (full-pointer`, que se move conforme elementos são retirados ou adicionados).

Um vetor é um array de rank 1 (de uma dimensão). Ele também é uma sequência.

Um simple vector é um simple array que não é especializado (não é usado :element-typepara definir o tipo dos elementos).

Criar um array, com uma ou mais dimensões

make-array (sizes-list :adjustable bool)

adjust-array (array, sizes-list, :element-type, :initial-element)

Acesso: aref (array i [j ...])

aref (array i j k ...) ou row-major-aref (array i) equivalente a (aref i i i ...).

O resultado é passível de setf.

(defparameter myarray (make-array '(2 2 2) :initial-element 1))
myarray
;; => #3A(((1 1) (1 1)) ((1 1) (1 1)))
(aref myarray 0 0 0)
;; => 1
(setf (aref myarray 0 0 0) 9)
;; => 9
(row-major-aref myarray 0)
;; => 9

Tamanhos

array-total-size (array i): quantos elementos cabem no array ?

array-dimensions (array): uma lista contendo o tamanho das dimensões do array.

array-dimension (array i): tamanho da i-ésima dimensão.

array-rank: número de dimensões do array.

(defparameter myarray (make-array '(2 2 2)))
;; => MYARRAY
myarray
;; => #3A(((0 0) (0 0)) ((0 0) (0 0)))
(array-rank myarray)
;; => 3
(array-dimensions myarray)
;; => (2 2 2)
(array-dimension myarray 0)
;; => 2
(array-total-size myarray)
;; => 8

Vectors

Crie com vector ou com a reader macro (macro de leitura) #(). Retorna um simple vector.

(vector 1 2 3)
;; => #(1 2 3)
#(1 2 3)
;; => #(1 2 3)

vector-push (foo vector): substitui o elemento de vector assinalado pelo fill pointer por foo. Pode ser destrutiva.

vector-push-extend *(foo vector [extension-num])*t

vector-pop (vector): retorna o elemento de vector que fill pointer aponta.

fill-pointer (vector). Passível de setf.

Veja também as funções de sequence.

Transformando um vector em uma list.

Se você está mapeando a list, veja a função map que tem como primeiro parâmetro o tipo resultante.

Ou use (coerce vector 'list).

Hash Table

Hash Tables são poderosas estruturas de dados, associando chaves com valores de uma forma muito eficiente. Hash Tables são preferíveis à association lists em casos que performance é essencial, porém introduzem um pouco de overhead, o que torna assoc lists melhores caso a quantidade de pares key-value seja baixa.

Mas às vezes Alists podem ser usadas de forma diferentes:

  • podem ser ordenadas
  • é possível fazer um push em cons cells que possuem a mesma key, remover a primeira e obter uma pilha
  • possuem uma representação sã aos olhos humanos
  • podem ser fácilmente (de)serializadas
  • devido ao RASSOC, chaves e valores em alists são essêncialmente intercâmbiáveis; já em hash tables chaves e valores desempenham papéis bem diferentes (Veja CLRecipes para mais).

Criando uma Hash Table

Hash Tables são criadas usando a função make-hash-table. Ela não requer argumentos, e o seu argumento mais usado é :test, especificando a função usada para testar a equalidade das chaves.

Se a biblioteca do cl21 for usada, é possível criar a hash table e adicionar elementos ao mesmo tempo com a nova sintáxe do reader #H :

(defparameter *my-hash* #H(:name "Eitaro Fukamachi"))

e acessamos um elemento com

(getf *my-hash* :name)

Obtendo um valor de uma Hash Table

A função gethash recebe dois argumentos obrigatórios: uma chave, e uma hash table. E retorna dois valores: o valor correspondente à chave na hash table (ou nil se não for encontrado), e um boolean indicando se a chave foi encontrada ou não. O segundo valor é necessário, já que nil é um valor válido em um par chave-valor, então receber nil como primeiro argumento de gethash não significa necessáriamente que a chave não foi encontrada.

Obtendo uma chave que não existe com um valor default

gethash poossui um terceiro argumento opcional

(gethash 'bar *my-hash* "default-bar")
;; => "default-bar"
;;     NIL

Obtendo todas as chaves ou todos os valores de uma hash table

A biblioteca Alexandria (em Quicklisp) tem as funções hash-table-keys e hash-table-values com esse propósito.

(ql:quickload :alexandria)
;; […]
(alexandria:hash-table-keys *my-hash*)
;; => (BAR)

Adicionando um elemento a uma Hash Table

Se deseja adicionar um elemento a uma hash table, você pode usar a função gethash para obter os elementos da hash table, em conjunto com setf.

CL-USER> (defparameter *my-hash* (make-hash-table))
*MY-HASH*
CL-USER> (setf (gethash 'one-entry *my-hash*) "one")
"one"
CL-USER> (setf (gethash 'another-entry *my-hash*) 2/4)
1/2
CL-USER> (gethash 'one-entry *my-hash*)
"one"
T
CL-USER> (gethash 'another-entry *my-hash*)
1/2
T

Testando a presença de uma chave em uma Hash Table

O primeiro valor retornado por gethash é o objeto da hash table associado à chave provida por você como argumento a gethash, ou nil caso nenhum valor exista para esta chave. Esse valor pode ser usado como um boolean generalizado se você deseja testar a existência de chaves.

CL-USER> (defparameter *my-hash* (make-hash-table))
*MY-HASH*
CL-USER> (setf (gethash 'one-entry *my-hash*) "one")
"one"
CL-USER> (if (gethash 'one-entry *my-hash*)
           "Key exists"
           "Key does not exist")
"Key exists"
CL-USER> (if (gethash 'another-entry *my-hash*)
           "Key exists"
           "Key does not exist")
"Key does not exist"

Mas perceba que isso não funciona caso nil esteja entre os valores que você deseja pôr no hash.

CL-USER> (setf (gethash 'another-entry *my-hash*) nil)
NIL
CL-USER> (if (gethash 'another-entry *my-hash*)
           "Key exists"
           "Key does not exist")
"Key does not exist"

Neste caso, você deve verificar o segundo valor de retorno de gethash que sempre retornará nil se nenhum valor for encontrado, e T caso contrário.

CL-USER> (if (nth-value 1 (gethash 'another-entry *my-hash*))
           "Key exists"
           "Key does not exist")
"Key exists"
CL-USER> (if (nth-value 1 (gethash 'no-entry *my-hash*))
           "Key exists"
           "Key does not exist")
"Key does not exist"

Deletando de uma Hash Table

Use remhash para deletar uma entrada. Tanto a chave quanto seu valor associado serão removidos da tabela. remhash retorna T se a entrada existe, e nil se não.

CL-USER> (defparameter *my-hash* (make-hash-table))
*MY-HASH*
CL-USER> (setf (gethash 'first-key *my-hash*) 'one)
ONE
CL-USER> (gethash 'first-key *my-hash*)
ONE
T
CL-USER> (remhash 'first-key *my-hash*)
T
CL-USER> (gethash 'first-key *my-hash*)
NIL
NIL
CL-USER> (gethash 'no-entry *my-hash*)
NIL
NIL
CL-USER> (remhash 'no-entry *my-hash*)
NIL
CL-USER> (gethash 'no-entry *my-hash*)
NIL
NIL

Percorrendo uma Hash Table

Se você deseja realizar uma ação em cada par chave-valor em uma hash table você pode usar:

maphash que itera sobre todas as entradas na tabela. Seu primeiro argumento deve ser uma função que aceita dois argumentos, a chave e o valor de cada entrada.

Perceba que pela natureza das Hash Tables é impossível controlar a ordem em que as entradas são providas pela maphash (ou outras funções que percorram hash tables). maphash sempre retorna nil.

CL-USER> (defparameter *my-hash* (make-hash-table))
*MY-HASH*
CL-USER> (setf (gethash 'first-key *my-hash*) 'one)
ONE
CL-USER> (setf (gethash 'second-key *my-hash*) 'two)
TWO
CL-USER> (setf (gethash 'third-key *my-hash*) nil)
NIL
CL-USER> (setf (gethash nil *my-hash*) 'nil-value)
NIL-VALUE
CL-USER> (defun print-hash-entry (key value)
    (format t "The value associated with the key ~S is ~S~%" key value))
PRINT-HASH-ENTRY
CL-USER> (maphash #'print-hash-entry *my-hash*)
The value associated with the key FIRST-KEY is ONE
The value associated with the key SECOND-KEY is TWO
The value associated with the key THIRD-KEY is NIL
The value associated with the key NIL is NIL-VALUE

Também é possível usar with-hash-table-iterator, uma macro que faz do seu primeiro argumento um iterador (usando macrolet) que retorna três valores por hash table para cada vez que for invocada - um boolean generalizado que é true se uma entrada for retornada, a chave para a entrada, e o valor da entrada. Se não houverem mais entradas, apenas o valor é rotornado - nil

;;; same hash-table as above
CL-USER> (with-hash-table-iterator (my-iterator *my-hash*)
           (loop
              (multiple-value-bind (entry-p key value)
                  (my-iterator)
                (if entry-p
                    (print-hash-entry key value)
                    (return)))))
The value associated with the key FIRST-KEY is ONE
The value associated with the key SECOND-KEY is TWO
The value associated with the key THIRD-KEY is NIL
The value associated with the key NIL is NIL-VALUE
NIL

TODO: Consigo traduzir isso não moisés Tome nota da seguinte ressalva do Hyperspec: "Não é especificado o que pode acontecer se qualquer estado interior implícito de uma iteração for retornada fora da extensão dinamica da form with-hash-table-iterator, como retornar um término da form de invocação.

E sempre existe a opção do loop:

;;; same hash-table as above
CL-USER> (loop for key being the hash-keys of *my-hash*
           do (print key))
FIRST-KEY
SECOND-KEY
THIRD-KEY
NIL
NIL
CL-USER> (loop for key being the hash-keys of *my-hash*
           using (hash-value value)
           do (format t "The value associated with the key ~S is ~S~%" key value))
The value associated with the key FIRST-KEY is ONE
The value associated with the key SECOND-KEY is TWO
The value associated with the key THIRD-KEY is NIL
The value associated with the key NIL is NIL-VALUE
NIL
CL-USER> (loop for value being the hash-values of *my-hash*
           do (print value))
ONE
TWO
NIL
NIL-VALUE
NIL
CL-USER> (loop for value being the hash-values of *my-hash*
           using (hash-key key)
           do (format t "~&~A -> ~A" key value))
FIRST-KEY -> ONE
SECOND-KEY -> TWO
THIRD-KEY -> NIL
NIL -> NIL-VALUE
NIL

**TODO: Procurar link certo pro cl21 Por último, existe também o (doeach ((key val) *hash*) ...) de cl21.

Percorrendo chaves ou valores

Para fazer um map pelas chaves ou pelos valores, é possível usar maphash-keys e maphash-values de Alexandria.

Contando as entradas em uma Hash Table

Não é preciso usar seus dedos (rs) - Common Lisp já possui uma função para isso hash-table-count.

CL-USER> (defparameter *my-hash* (make-hash-table))
*MY-HASH*
CL-USER> (hash-table-count *my-hash*)
0
CL-USER> (setf (gethash 'first *my-hash*) 1)
1
CL-USER> (setf (gethash 'second *my-hash*) 2)
2
CL-USER> (setf (gethash 'third *my-hash*) 3)
3
CL-USER> (hash-table-count *my-hash*)
3
CL-USER> (setf (gethash 'second *my-hash*) 'two)
TWO
CL-USER> (hash-table-count *my-hash*)
3
CL-USER> (clrhash *my-hash*)
#<EQL hash table, 0 entries {48205F35}>
CL-USER> (hash-table-count *my-hash*)
0

Problemas de Performance: Tamanho da Hash Table

A função make-hash-table possui alguns parâmetros opcionais que controlam o tamanho inicial da sua Hash Table e de que forma ela vai crescer, caso preciso. Esse pode ser um iimportante problema de performance se você está trabalhando com hash tables grandes. Aqui está um exemplo (não muito científico) usando CMUCL pre-18d em um sistema Linux:

CL-USER> (defparameter *my-hash* (make-hash-table))
*MY-HASH*
CL-USER> (hash-table-size *my-hash*)
65
CL-USER> (hash-table-rehash-size *my-hash*)
1.5
CL-USER> (time (dotimes (n 100000) (setf (gethash n *my-hash*) n)))
Compiling LAMBDA NIL:
Compiling Top-Level Form:

Evaluation took:
  0.27 seconds of real time
  0.25 seconds of user run time
  0.02 seconds of system run time
  0 page faults and
  8754768 bytes consed.
NIL
CL-USER> (time (dotimes (n 100000) (setf (gethash n *my-hash*) n)))
Compiling LAMBDA NIL:
Compiling Top-Level Form:

Evaluation took:
  0.05 seconds of real time
  0.05 seconds of user run time
  0.0 seconds of system run time
  0 page faults and
  0 bytes consed.
NIL

Os valores para hash-table-size e para hash-table-rehash-size são dependentes da implementação. No nosso caso, CMUL escolhe um tamanho inicial de 65, e vai aumentar o tamanho da tabela em 50% sempre que ela tiver que crescer. Vejamos quantas vezes nós vamos ter que redimensionar o tamanho da nossa hash table até que ela chegue ao seu tamanho final...

CL-USER> (log (/ 100000 65) 1.5)
18.099062
CL-USER> (let ((size 65)) (dotimes (n 20) (print (list n size)) (setq size (* 1.5 size))))
(0 65)
(1 97.5)
(2 146.25)
(3 219.375)
(4 329.0625)
(5 493.59375)
(6 740.3906)
(7 1110.5859)
(8 1665.8789)
(9 2498.8184)
(10 3748.2275)
(11 5622.3413)
(12 8433.512)
(13 12650.268)
(14 18975.402)
(15 28463.104)
(16 42694.656)
(17 64041.984)
(18 96062.98)
(19 144094.47)
NIL

Ele teve que crescer 19 vezes até ser grande o suficiente para comportar 100.000 entradas. Isto explica o porque de tantos consings e a relativa demora para preencher a tabela. Explica também porque a segunda vez foi bem mais rápida - a hash table já estava com seu tamanho correto.

Aqui está uma forma mais rápida de fazer esse redimensionamento: Se já soubermos de antemão quão grande a nossa tabela será, nós podemos de cara começar com o tamanho exato:

CL-USER> (defparameter *my-hash* (make-hash-table :size 100000))
*MY-HASH*
CL-USER> (hash-table-size *my-hash*)
100000
CL-USER> (time (dotimes (n 100000) (setf (gethash n *my-hash*) n)))
Compiling LAMBDA NIL:
Compiling Top-Level Form:

Evaluation took:
  0.04 seconds of real time
  0.04 seconds of user run time
  0.0 seconds of system run time
  0 page faults and
  0 bytes consed.
NIL

Esta forma é obviamente muito mais rápida. Além disso não envolveu consing de forma alguma, pois não houve redimensionamento. Se não se sabe o tamanho final, mas é possível estimar a taxa de crescimento da hash table, pode-se passar este valor para make-hash-table. Ao se passar um número inteito especifica-se um crescimento absoluto, já um numero de ponto flutuante um crescimento relativo.

CL-USER> (defparameter *my-hash* (make-hash-table :rehash-size 100000))
*MY-HASH*
CL-USER> (hash-table-size *my-hash*)
65
CL-USER> (hash-table-rehash-size *my-hash*)
100000
CL-USER> (time (dotimes (n 100000) (setf (gethash n *my-hash*) n)))
Compiling LAMBDA NIL:
Compiling Top-Level Form:

Evaluation took:
  0.07 seconds of real time
  0.05 seconds of user run time
  0.01 seconds of system run time
  0 page faults and
  2001360 bytes consed.
NIL

Relativamente rápido (apenas um redimensionamento é necessário) mas com muito mais consing, pois quase toda a hash table (tirando os 65 elementos iniciais) teve de ser construída durante o loop.

Note também que você pode especificar o rehash-threshold enquanto cria uma nova hash table. E por último, sua implementação pode ignorar completamente os valores os valores passados por rehash-size e rehash-threshold ...

Alist

Umas lista associativa é uma lista de cons cells.

Este simples exemplo:

(defparameter *my-alist* (list (cons 'foo "foo")
                             (cons 'bar "bar")))
;; => ((FOO . "foo") (BAR . "bar"))

fica assim:

[o|o]---[o|/]
 |       |
 |      [o|o]---"bar"
 |       |
 |      BAR
 |
[o|o]---"foo"
 |
FOO

Nós podemos construir uma alist de acordo com sua representação:

(setf *my-alist* '((:foo . "foo")
                 (:bar . "bar")))

O construtor pairlis associa uma lista de chaves a uma lista de valores:

(pairlis '(:foo :bar)
         '("foo" "bar"))
;; => ((:BAR . "bar") (:FOO . "foo"))

Para pegar uma chave, nós temos assoc (use :test 'equal quando suas chaves forem strings, como sempre). Ela retorna a toda a cons cell, então você pode usar cdr ou second para obter o valor, ou ainda melhor, assoc-value list key de Alexandria.

(alexandria:assoc-value *my-alist* :foo)
;; it actually returns 2 values
;; "foo"
;; (:FOO . "FOO")

Existem assoc-if, e rassoc para obter uma cons cell a partir do seu valor.

Para adicionar uma chave, nós fazemos push para outra cons cell:

(push (cons 'team "team") *my-alist*)
;; => ((TEAM . "team") (FOO . "foo") (BAR . "bar"))

Podemos usar pop e outras funções que operam sobre listas, como remove:

(remove :team *my-alist*)
;; => ((:TEAM . "team") (FOO . "foo") (BAR . "bar")) ;; didn't remove anything
(remove :team *my-alist* :key 'car)
;; => ((FOO . "foo") (BAR . "bar")) ;; returns a copy

Remover apenas um elemento com :count:

(push (cons 'bar "bar2") *my-alist*)
;; => ((BAR . "bar2") (TEAM . "team") (FOO . "foo") (BAR . "bar")) ;; twice the 'bar key
(remove 'bar *my-alist* :key 'car :count 1)
;; => ((TEAM . "team") (FOO . "foo") (BAR . "bar"))
;; because otherwise:
(remove 'bar *my-alist* :key 'car)
;; => ((TEAM . "team") (FOO . "foo")) ;; no more 'bar

Na biblioteca Alexandria veja mais funções como remove-from-plist, alist-plist, ...

Plist

Uma property list (lista de propriedades) é simplesmente uma lista que alterna uma chave, um valor, etc, onde suas chaves são simbolos (não é possível utiilizar :test). Mais precisamente, a plist tem um cons cell em que car é a chave, que cdr aponta para a próxima cons cell em que car seja o valor.

Esta plist por exemplo:

(defparameter my-plist (list 'foo "foo" 'bar "bar"))

é representada assim:

[o|o]---[o|o]---[o|o]---[o|/]
 |       |       |       |
FOO     "foo"   BAR     "bar"

Acessamos o valor com getf (list elt) (retorna o valor) (a lista é o primeiro elemento),

removemos um elemento com remf.

(defparameter my-plist (list 'foo "foo" 'bar "bar"))
;; => (FOO "foo" BAR "bar")
(setf (getf my-plist 'foo) "foo!!!")
;; => "foo!!!"

Structures

Estruturas oferecem um maneira de armazenar dados em espaços determinados. Elas suportam single inheritance.

Classes providas pelo Common Lisp Object System (CLOS) são mais flexiveis, porém estruturas podem oferecer melhor performance (veja o exemplo no manual do SBCL).

Criação

defstruct

(defstruct person
   id name age)

Determinar espaços na momento de criação é opcional e por default nil.

Para colocar um valor default:

(defstruct person
   id
   (name "john doe")
   age)

E para especificar o tipo após o valor default:

(defstruct person
  id
  (name "john doe" :type string)
  age)

Se cria uma instância usando o construtor gerado da forma make- + <nome-da-estrutura>, ou seja, make-person:

(defparameter *me* (make-person))
*me*
#S(PERSON :ID NIL :NAME "john doe" :AGE NIL)

note que representações com o print podem ser lidas de volta com o reader.

Com um nome de tipo inválido:

(defparameter *bad-name* (make-person :name 123))
Invalid initialization argument:
  :NAME
in call for class #<STRUCTURE-CLASS PERSON>.
   [Condition of type SB-PCL::INITARG-ERROR]

Nós podemos definir o construtor da estrutura para que a estrutura seja criada sem usar argumentos de keywords, o que às vezes pode ser mais útil. Nós passamos o nome e a ordem deos argumentos:

(defstruct (person (:constructor create-person (id name age)))
     id
     name
     age)

Nosso novo construtor é create-person:

(create-person 1 "me" 7)
#S(PERSON :ID 1 :NAME "me" :AGE 7)

Porém, o default make-person deixa de funcionar:

(make-person :name "me")
;; debugger:
obsolete structure error for a structure of type PERSON
[Condition of type SB-PCL::OBSOLETE-STRUCTURE]

Slot access

É possível acessar os slots com accessors criados por <name-of-the-struct>- + slot-name:

(person-name *me*)
;; "john doe"

Setting

Slots são passíveis de setf:

(setf (person-name *me*) "Cookbook author")
(person-name *me*)
;; "Cookbook author"

Predicado

(person-p *me*)
T

Single inheritance

Usando o argumento :include <struct>:

(defstruct (female (:include person))
     (gender "female" :type string))
(make-female :name "Lilie")
;; #S(FEMALE :ID NIL :NAME "Lilie" :AGE NIL :GENDER "female")

Limitações

Depois de uma mudança, intânncias não sofrem updates.

Se nós tentarmos adicionar um slot (email abaixo), temos a escolha de perder todas as instâncias ou de continuar usando a nova definição de person, mas os efeitos de redefinir uma estrutura não são determinados pelo padrão, então o melhor a se fazer é recompilar e rodar novamente a parte que sofreu mudança.

(defstruct person
       id
       (name "john doe" :type string)
       age
       email)

attempt to redefine the STRUCTURE-OBJECT class PERSON
incompatibly with the current definition
   [Condition of type SIMPLE-ERROR]

Restarts:
 0: [CONTINUE] Use the new definition of PERSON, invalidating already-loaded code and instances.
 1: [RECKLESSLY-CONTINUE] Use the new definition of PERSON as if it were compatible, allowing old accessors to use new instances and allowing new accessors to use old instances.
 2: [CLOBBER-IT] (deprecated synonym for RECKLESSLY-CONTINUE)
 3: [RETRY] Retry SLIME REPL evaluation request.
 4: [*ABORT] Return to SLIME's top level.
 5: [ABORT] abort thread (#<THREAD "repl-thread" RUNNING {1002A0FFA3}>)

Se escolhermos reiniciar 0, para usar a nova definição, não poderemos acessar *me*:

*me*
obsolete structure error for a structure of type PERSON
   [Condition of type SB-PCL::OBSOLETE-STRUCTURE]

Portable Common Lisp não define maneiras de descobrir super/sub-structures, nem que slots uma estrutura possui.

Common Lisp Object System (que veio depois da linguagem) não tem tais limitações. Veja a seção sobre CLOS.

Tree

tree-equal, copy-tree. Elas descendem recursivamente no car e no cdr das cons cells que visitam;

Sycamore - árvores-binárias balanceadas puramente funcionais

https://github.com/ndantam/sycamore

Features:

  • Árvores-binárias balanceadas puramente funcionais velozes.
  • Folhas são vetores simples, reduzindo a altura da árvore.
  • Interfaces para Sets e Maps para árvores.
  • Ropes.
  • pairing heaps puramente funcionais
  • Filas amortizadas puramente funcionais

Apêndice A - acesso genérico de alists, plists, hash-tables e CLOS slots

As soluções apresentadas abaixo pode lhe ajudar no começo, mas tenha em mente que elas terão impactos em performance e mensagens de erro serão menos explícitas.

  • CL21 tem um getf genérico (bem como outras funções genéricas),
  • rutils como um generic-elt genérico, ou ?,
  • a biblioteca access (testada em batalha, usada pelo sistema de templating Djula) tem um generico (access my-var :elt) (blog post).

Apêndice B - acessando estruturas de dados aninhadas

As vezes trabalhamos com estruturas de dados aninhadas, e queremos uma maneira mais simples de acessar um elemento aninhado comparado a um intrincado "getf" e "assoc" etc. Além disso, quando uma chave intermediária não existe seja retornado apenas um nil.

A biblioteca access mostrada acima provê isso, com (accesses var key1 key2...).