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
- Marco Antoniotti
- Zach Beane
- Pierpaolo Bernardi
- Christopher Brown
- Frederic Brunel
- Jeff Caldwell
- Bill Clementson
- Martin Cracauer
- Gerald Doussot
- Paul Foley
- Jörg-Cyril Höhle
- Nick Levine
- Austin King
- Lieven Marchand
- Drew McDermott
- Kalman Reti
- Alberto Riva
- Rudi Schlatte
- Emre Sevinç
- Paul Tarvydas
- Kenny Tilton
- Reini Urban
- Matthieu Villeneuve
- Edi Weitz
- Fernando Borretti
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
- lisp-lang.org
- A lista Awesome-cl
- O Common Lisp HyperSpec, por Kent M. Pitman
- O Common Lisp UltraSpec
- Practical Common Lisp, por Peter Seibel
- Common Lisp Recipes por Edmund Weitz, publicado em 2016
- Cliki, a Wiki de Common Lisp
- Articulate Common Lisp, um tutorial de iniciação para os não-iniciados
- Common Lisp no Wikibooks
- O velho FAQ do comp.lang.lisp, por Mark Kantrowitz
- Common Lisp: A Gentle Introduction to Symbolic Computation, por David S. Touretzky
- Successful Lisp: How to Understand and Use Common Lisp, por David B. Lamkins
- On Lisp, por Paul Graham
- Common Lisp the Language, 2nd Edition, por Guy L. Steele
- Common Lisp Hints, por Geoffrey J. Gordon
- A Guide to CLOS, por Jeff Dalton
- Common Lisp Pitfalls, por Jeff Dalton
- Tutorial para o macro Loop de Common Lisp, por Peter D. Karp
- Um Tutorial para um bom estilo de Lisp, por Peter Norvig e Kent Pitman
- Lisp and Elements of Style, por Nick Levine
- Guia Altamente Opinativo de Lisp, de Pascal Constanza
- Loving Lisp - the Savy Programmer's Secret Weapon, por Mark Watson
- FranzInc, uma empresa que vende soluções em Common Lisp e Bancos de Dados Gráficos.
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 defpackage
s, 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)
- Documentação do ASDF: definindo um sistema com
defsystem
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-c
1 (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
- Organização de código-fonte, libraries e packages: https://lispmethods.com/libraries.html
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
- Let over Lambda seção abordando expressões cíclicas
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-type
para 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.
- structures on the hyperspec
- David B. Lamkins, "Successful Lisp, How to Undertsand and Use Common Lisp".
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...)
.