terça-feira, 9 de dezembro de 2008

Americanas: "inconsistências"

Há umas semanas tentei comprar um notebook das Americanas. Um dia após eu ter feito o pedido, recebo um e-mail pedindo para enviar dados a serem avaliados pela instituição financeira responsável pelo meu cartão de crédito. No mesmo dia respondi o e-mail com as informações solicitadas (com as mesmas informações que usei em compras feitas nas Americanas anteriormente).

Dois dias depois recebo um e-mail indicando que as informações haviam sido recebidas e que a análise demoraria até 36h. Depois de mais dois dias recebo um e-mail informando que o pedido havia sido suspenso devido a inconsistência de informações na sua compra e também à ausência de contato nossa Central de Atendimento. Ressalto que respondi todas as requisições de contato e pedidos de informações que a mim chegaram.

Via e-mail, tentei obter informações sobre a razão da suspensão do pedido, mas não obtive resposta.

Hoje fui ver a fatura do meu cartão de crédito e percebi que o valor da parcela da compra havia sido debitado. É claro, ficou a dúvida: se as informações eram inconsistentes para efetuar a compra, por que não eram inconsistentes para fazer a cobrança?

Tentei fazer esta pergunta para o atendimento online das Americanas, via chat. A conversa está abaixo (fui fazendo a captura das telas conforme a conversa se desenvolvia, pois temia que o texto fosse desaparecer quando o atentente desejasse -- o que de fato aconteceu).
















Não consegui capturar a última tela, pois o atendente "desligou".

Fica o conselho: cautela ao comprar das Americanas.

sexta-feira, 5 de dezembro de 2008

Interpretador de COW em Chicken Scheme

Dando seqüência à série implementações de interpretadores de linguagens que não servem para nada (iniciada com um interpretador de assembly), a seguir está a implementação de um interpretador para a linguagem COW em Chicken Scheme.

Da página da linguagem:

The COW programming language was designed with the bovine in mind. Given that cows have somewhat limited vocabulary skills, it seemed natural to incorporate only the words they know into the language. As a result, all instructions are some variation on "moo" which just so happens to be the only word they really understand.

Abaixo estão o código da implementação e um programa exemplo:

(use srfi-1)

(define *memory* (make-vector 1 0))
(define *program* #f)
(define *ip* 0) ;; instruction pointer
(define *mp* 0) ;; memory pointer
(define *register* #f)
(define instructions
'(("moo" . 0) ("mOo" . 1) ("moO" . 2) ("mOO" . 3) ("Moo" . 4)
("MOo" . 5) ("MoO" . 6) ("MOO" . 7) ("OOO" . 8) ("MMM" . 9)
("OOM" . 10) ("oom" . 11)))

(define (sanitize-code code)
;; removes non-cow instructions and returns a list containing valid cow code
(map string->symbol
(filter (cut alist-ref <> instructions equal?)
(flatten (map string-split code)))))

(define (get-address snippet from to)
(let ((scope 0)
(address #f))
(let loop ((code snippet))
(if (and (null? code) (not address))
(terminate)
(let* ((current-instr (car code))
(instr (cdr current-instr))
(pos (car current-instr)))
(cond ((eq? instr from)
(set! scope (add1 scope))
(loop (cdr code)))
((and (not (zero? scope)) (eq? instr to))
(set! scope (sub1 scope))
(loop (cdr code)))
((and (zero? scope) (eq? instr to)) pos)
(else (loop (cdr code)))))))))

(define (MOO-address)
(get-address (reverse (take *program* (sub1 *ip*))) 'moo 'MOO))

(define (moo-address)
(get-address (drop *program* (+ 2 *ip*)) 'MOO 'moo))

(define (mem-get) (vector-ref *memory* *mp*))

(define (maybe-resize-mem!)
(let ((vlen (vector-length *memory*)))
(when (>= (add1 *mp*) vlen)
(set! *memory* (vector-resize *memory* (add1 vlen) 0)))))

(define (mem-set! val)
(maybe-resize-mem!)
(vector-set! *memory* *mp* val))

(define (terminate #!optional (exit-code 0))
(print "Terminating.")
(exit exit-code))

(define (moo-interpreter file)
(let* ((sane-code (sanitize-code (read-lines file)))
(program-len (length sane-code))
(debug? (getenv "COWDEBUG")))
(set! *program* (map cons (iota program-len) sane-code))
(when debug? (display "prog> ") (pp *program*))
(let loop ()
(when debug?
(print "-----------------------------------")
(print *program*)
(display "mem> ") (pp *memory*)
(print "*mp*> " *mp*)
(print "*ip*> " *ip*)
(print "*register*> " *register*))
(when (> *ip* program-len) (terminate))
(if (null? *program*)
(terminate)
(let* ((current-instr (alist-ref *ip* *program*))
;; special-instr => instructions that modify *ip*
(special-instrs '(MOO mOO moo))
(special-instr? (memq current-instr special-instrs)))
(when (eq? current-instr 'mOO)
(unless (memq (mem-get) (map cdr instructions))
(terminate))
(set! current-instr (alist-ref (mem-get) *program*))
(set! special-instr? (memq current-instr special-instrs)))
(when debug?
(display "current-instr> ")
(display (if special-instr? "(*) " ""))
(pp current-instr))
(case current-instr
((moo) (set! *ip* (MOO-address)))
((mOo) (maybe-resize-mem!) (set! *mp* (sub1 *mp*)))
((moO) (maybe-resize-mem!) (set! *mp* (add1 *mp*)))
((Moo) (if (zero? (mem-get))
(mem-set! (string->number (->string (read-line))))
(print (integer->char (mem-get)))))
((MOo) (mem-set! (sub1 (mem-get))))
((MoO) (mem-set! (add1 (mem-get))))
((MOO) (if (zero? (mem-get))
(set! *ip* (add1 (moo-address)))
(set! *ip* (add1 *ip*))))
((OOO) (mem-set! 0))
((MMM) (if *register*
(begin (mem-set! *register*)
(set! *register* #f))
(set! *register* (mem-get))))
((OOM) (print (mem-get)))
((oom) (mem-set! (string->number (read-line)))))
(unless special-instr? (set! *ip* (add1 *ip*)))))
(loop))))

(define (usage #!optional (exit-code 0))
(print "Usage: " (program-name) " <input file>")
(exit exit-code))

(let ((args (command-line-arguments)))
(if (null? args)
(usage 1)
(let ((file (car args)))
(if (file-exists? (car args))
(moo-interpreter file)
(begin
(print file ": not found.")
(exit 1))))))


Obs.: se a variável COWDEBUG estiver definida no ambiente, o interpretador exibe informações para depuração.

Aqui está a implementação de um gerador da série de Fibonacci em COW (um dos exemplos disponibilizados no site da linguagem):


MoO
moO
MoO
mOo
[[ main loop ]]
MOO
[[ print first number ]]
OOM
[[ temp copy of first number ]]
MMM
moO
moO
MMM
mOo
mOo
[[ store second number off in the first position now ]]
moO
MMM
mOo
MMM
[[ move back to temp number ]]
moO
moO
[[ use temp to add to first and store in second in loop ]]
MOO
MOo
mOo
MoO
moO
moo
mOo
mOo
moo


Executando este exemplo com o interpretador, temos:


$ csi -s cow.scm fib.cow
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
...


Algum voluntário para implementar as Distributed Digestion eXtentions? :-)

quarta-feira, 19 de novembro de 2008

Scheme na Apple?

Estava dando uma olhada em uma apresentação sobre o MacOSX que o Drebes divulgou em uma lista de discussão e me deparei com a seguinte tela:



Com uma rápida pesquisada, encontrei o seguinte texto:


While there is no documentation of the format of the policy file, there are several examples available in /usr/share/sandbox which give clues to what a policy can do. These policies seem to use a Scheme-like syntax and provide abilities to restrict file access (based on pathname), restrict interprocess communication (IPC) such as shared memory, restrict network access, restrict signals, restrict many process-related actions, restrict sysctls, and more.


Interessante.

terça-feira, 28 de outubro de 2008

Chicken LiveCD na LatinoWare 2008


Como não será possível levar a torradeira para a Latinoware 2008, levarei alguns LiveCDs de Chicken para os interessados. Serão poucas unidades (~20). Faça sua reserva na seção de comentários. :-)

quarta-feira, 22 de outubro de 2008

Chicken LiveCD 0.4

A versão 0.4 do Chicken LiveCD está disponível em http://g3pd.ufpel.tche.br/chicken/livecd/.

Esta versão contém:



Esta versão do LiveCD é baseada no Ubuntu. Usei o Reconstructor para remasterizar o CD.

segunda-feira, 20 de outubro de 2008

Correctly answer a simple skill testing question

Ainda não decidi se devo chorar ou dar risada:



Há uma thread na lista de discussão da implementação PLT com a discussão gerada por uma simples questão.

sexta-feira, 17 de outubro de 2008

Lisp na LatinoWare 2008!

A LatinoWare 2008, V Conferência Latino-Americana de Software Livre, que ocorrerá de 30 de outubro a 1o. de novembro em Foz do Iguaçu, PR, contará com três apresentações especificamente sobre Lisp! Serão 3 horas consecutivas sobre parênteses no dia 31/10/2008:

15h-16hUsando Common Lisp no Dia-a-diaPedro Ribeiro Kroger Junior
16h-17hChicken - Uma Implementação de Scheme para Aplicações PráticasMario Domenech Goulart
17h-18hCL-Weblocks - Programando Aplicações Web com BlocosVilson Vieira da Silva Junior


A programação completa do evento está em http://lapsi.latinoware.org/index.php?page=grade.GradeEvento&id=1.

quinta-feira, 4 de setembro de 2008

Interface para manipulação de grupos no LDAP

A seguir está uma simples, rápida e não muito elegante interface para manipulação de grupos de usuários no LDAP (Lightweight Directory Access Protocol) que usa o programa cpu por baixo dos panos. A implementação é em Chicken Scheme.

Com esta ferramenta são possíveis as seguintes operações:

  • Listar grupos aos quais um dado usuário pertence

  • Listar usuários que fazem parte de um dado grupo

  • Adicionar usuário a grupo


Exemplos:


$ ldap-groups.scm -h
Uso: ldap-groups.scm -h | -a <user> <group> | -l <group> | -g <user>

-a : adiciona <user> ao grupo <group> (cria <group> se nao existir)
-l : lista os usuarios do grupo <group>
-g : lista os grupos a que pertence o usuario <user>

Listando os usuários do grupo admin:


$ ldap-groups.scm -l admin
mario
joao

Listando os grupos a que pertenço:


$ ldap-groups.scm -g mario
sgpc
admin

Adicionando o meu usuário ao grupo printer:


$ ldap-groups.scm -a mario printer

O código está abaixo:

(use posix srfi-13)

(define (system-output . command)
(let ((cmd (string-intersperse (map ->string command))))
(read-all (open-input-pipe (sprintf "~A 2>&1" cmd)))))

(define *groups-data* #f)

(define (set-groups-data!)
(set! *groups-data*
;; lista de listas (("grupo" "user1" "user2" ... "usern"))
(let ((data (with-input-from-string
(cadr (string-split-fields
"Group Entries\n"
(system-output "cpu cat")
infix:))
read-lines)))
(map (lambda (line)
(let ((ldata (string-split line ":")))
(cons (car ldata) ;; nome do grupo
(let ((users (cdddr ldata))) ;; usuarios
(if (null? users)
'()
(map
string-trim-both
(string-split (car users) ",")))))))
data))))

(define (get-groups user)
(let ((user-groups '()))
(for-each (lambda (group/users)
(when (member user (cdr group/users))
(set! user-groups
(cons (car group/users) user-groups))))
*groups-data*)
user-groups))

(define (group-exists? group)
(not (not (alist-ref group *groups-data* equal?))))

(define (get-users group)
(alist-ref group *groups-data* equal?))

(define (add-user-to-group! user group)
(unless (group-exists? group)
(let ((cmd (conc "cpu groupadd " group)))
(print cmd)
(system-output cmd)))
(let ((cmd (conc "cpu usermod " user " -G "
(string-intersperse
(cons group (get-groups user)) ","))))
(print cmd)
(system-output cmd)))

(define (usage #!optional exit-code)
(print #<#EOF
Uso: #(program-name) -h | -a <user> <group> | -l <group> | -g <user>

-a : adiciona <user> ao grupo <group> (cria <group> se nao existir)
-l : lista os usuarios do grupo <group>
-g : lista os grupos a que pertence o usuario <user>
EOF
)
(when exit-code
(exit exit-code)))

(let ((args (command-line-arguments)))
(when (null? args)
(usage 1))
(set-groups-data!)
(cond ((member (car args) '("-h" "--help" "-help"))
(usage))
((member "-a" args)
(add-user-to-group! (cadr args) (caddr args)))
((member "-l" args)
(map print (or (get-users (cadr args)) '())))
((member "-g" args)
(map print (or (get-groups (cadr args)) '())))
(else (usage 1))))

segunda-feira, 11 de agosto de 2008

Configurando o tamanho do stack trace

Chicken dispõe de uma opção para configurar o tamanho do relatório de chamadas de procedimentos (stack trace) a ser exibido em caso de erro. Se a configuração for omitida, o valor 8 é usado, o que significa que serão exibidas as últimas 8 chamadas de procedimento.

Para algumas aplicações, esse número pode ser muito pequeno, mas pode ser aumentado com o parâmetro -:aNUMBER, onde NUMBER é o número de chamadas a ser mostrado (http://chicken.wiki.br/Using%20the%20compiler#runtime-options).

Abaixo está um exemplo de uma situação onde o aumento do stack trace pode ser útil:

(define (l) (error 'oops))
(define (k) (l))
(define (j) (k))
(define (i) (j))
(define (h) (i))
(define (g) (h))
(define (f) (g))
(define (e) (f))
(define (d) (e))
(define (c) (d))
(define (b) (c))
(define (a) (b))

(a)

Executando este código com o interpretador, temos:


$ csi -s oops.scm
Error: oops

Call history:

[e] (f)
[f] (g)
[g] (h)
[h] (i)
[i] (j)
[j] (k)
[k] (l)
[l] (error (quote oops)) <--

Com esse relatório, não fica claro que quem originou a chamada de l foi a. Mas, se aumentarmos o tamanho do relatório:


$ csi -:a14 -s oops.scm
Error: oops

Call history:

(a)
(a)
[a] (b)
[b] (c)
[c] (d)
[d] (e)
[e] (f)
[f] (g)
[g] (h)
[h] (i)
[i] (j)
[j] (k)
[k] (l)
[l] (error (quote oops)) <--

O parâmetro -:aNUMBER também é válido para programas compilados com o compilador de Chicken (csc):


$ csc oops.scm
$ ./oops
Error: oops

Call history:

oops.scm: 8 f
oops.scm: 7 g
oops.scm: 6 h
oops.scm: 5 i
oops.scm: 4 j
oops.scm: 3 k
oops.scm: 2 l
oops.scm: 1 error <--

$ ./oops -:a14
Error: oops

Call history:

oops.scm: 14 a
oops.scm: 12 b
oops.scm: 11 c
oops.scm: 10 d
oops.scm: 9 e
oops.scm: 8 f
oops.scm: 7 g
oops.scm: 6 h
oops.scm: 5 i
oops.scm: 4 j
oops.scm: 3 k
oops.scm: 2 l
oops.scm: 1 error <--

quarta-feira, 6 de agosto de 2008

Chicken Web REPL

Há algum tempo eu e o Vilson estávamos discutindo sobre REPLs na Web (não lembro o que desencadeou esse tópico -- também é possível que o tópico não tenha sido esse -- tá feio o caso da minha memória). Bem, o fato é que desta conversa surgiu a idéia de fazer um REPL via Web para Chicken. Lembrei do egg sandbox, do Chicken Playground (um ambiente chroot com uma instalação de Debian, Chicken e um monte de eggs) e fiz um Web REPL simples para Chicken.

Em seguida, o Vilson descobriu o EditArea (um editor de código em Javascript) e modificou para adicionar um suporte básico a Common Lisp. Enviou o código para mim e eu, com base nele, adicionei suporte básico a Scheme.

O resultado está em http://repl.ucpel.tche.br:8080.



Uma das funcionalidades interessantes do Web REPL é o uso de sessões HTTP para manter coisas como histórico de trechos de código submetidos ao avaliador e definições feitas na sessão. A implementação de sessões é feita com o egg http-session.

O Web REPL também usa os eggs web-scheme, ajax, spiffy-utils e spiffy (servidor web).

REPL de Chicken para acesso a bases de dados do Postgres

A seguir está uma forma de usar o REPL de Chicken (csi) como um REPL para bases de dados do Postgres.

Com isso, tem-se um REPL que possibilita a execução de consultas SQL e código Scheme. A implementação usa o próprio REPL do sistema Chicken e alguns eggs como: postgresql (para acesso so Postgres), readline (para edição de linhas de comando, histórico) e stty (para configuração do terminal na leitura de senhas).

O programa db-repl.scm usa como argumentos não interativos (opcionais) o usuário do banco de dados, o nome do host e a base de dados, os quais devem ser fornecidos no seguinte formato:

<usuario>@<host>/<base de dados>

Quando executado, o programa pede para o usuário digitar a senha.

A associação do REPL de Chicken com a base de dados é feita através da definição de comandos do REPL (toplevel-command). Na implementação mostrada abaixo, são definidos três comandos:

  • tables: mostras as tabelas da base de dados.

  • table: mostra estrutura da tabela dada como argumento (nome e tipo das colunas e se podem ou não ser nulas).

  • -: executa a consulta SQL dada como argumento.


Exemplos:

csi -s db-repl.scm mario@localhost/sgpc
Senha: *****

#;1> ,tables
perms
news
users_sites
ticket_comments
ticket_attachments
obj_type
acervos
autores
videos
audios
images
objects
texts
scanners
users
wiki
sites
tickets

#;1> ,table news
news_id integer NO
user_id integer NO
site_id integer NO
timestamp timestamp without time zone YES
title character varying(100) NO
news text YES

#;1> ,- select * from news
(#(2
1
1
#(2008 7 30 19 37 54 271625)
"Teste."
"teste

=== titulo")
#(3
1
1
#(2008 7 30 19 40 27 180765)
"Outra notícia!"
"Aqui vai o texto da notícia.

[[image:http://subversion.tigris.org/branding/images/logo.gif|Logo]]"))

Obviamente, os comandos para acesso ao banco de dados disponibilizados através do REPL podem ser estendidos. O texto PostgreSQL INFORMATION_SCHEMA fornece várias dicas de como extrair informações de bases de dados do Postgres.

Nesta implementação, o parâmetro pg-repl:conn armazena o objeto que representa a conexão com o banco de dados, de forma que ele pode ser usado pelos procedimentos do egg postgresql para a execução de consultas:


#;1> (define query "select * from news")
#;2> (vector-ref (car (pg:query-tuples query (pg-repl:conn))) 3)
#(2008 7 30 19 37 54 271625)

O código do programa (db-repl.scm) está a seguir:

(use utils postgresql readline stty regex (srfi 13))

(define pg-repl:conn (make-parameter #f))

(define (pg-repl:query . query)
(pg:query-tuples
(string-intersperse (map ->string query) "")
(pg-repl:conn)))

(toplevel-command
'-
(lambda ()
(pp (pg-repl:query
(with-output-to-string
(cut print (read-line)))))))

(toplevel-command
'table
(lambda ()
(let* ((table (string-trim-both (read-line)))
(cols
(pg-repl:query
"select column_name,data_type,"
"character_maximum_length,is_nullable "
"from information_schema.columns "
"where table_name = '" table "'")))
(if (null? cols)
(print "Tabela \"" table "\" nao existe.")
(for-each
(lambda (f)
(let ((colname (vector-ref f 0))
(type (vector-ref f 1))
(size (let ((size (vector-ref f 2)))
(if (pg:sql-null-object? size)
""
(conc "(" size ")"))))
(nullable (vector-ref f 3)))
(print
colname
(make-string
(- 30 (string-length colname)))
type size
(make-string
(- 30 (string-length (conc type size))))
nullable)))
cols)))))

(toplevel-command
'tables
(lambda ()
(for-each
(lambda (item)
(print (vector-ref item 0)))
(pg-repl:query
"select table_name from information_schema.tables "
"where table_type = 'BASE TABLE' "
"and table_schema not in "
"('pg_catalog', 'information_schema')"))))

(define (pg-repl:usage #!optional exit-code)
(print (program-name) " [<user>@<server>/<database>]")
(when exit-code (exit exit-code)))

(let ((args (command-line-arguments))
(user "postgres")
(host "localhost")
(db "template")
(passwd #f))

;; Restaura o terminal em caso de termino via C-c
(set-signal-handler! signal/int
(lambda (_) (stty '(echo))))

(unless (or (null? args) (equal? "\"\"" (car args)))
(let* ((cred (string-trim-both
(car args)
(cut memq <> '(#\space #\newline #\")))) ;"
(@tokens (string-split cred "@"))
(/tokens (if (null? @tokens)
'()
(string-split (cadr @tokens) "/"))))
(if (and (null? @tokens) (null? /tokens))
(pg-repl:usage 1)
(begin
(unless (null? @tokens)
(set! user (car @tokens)))
(unless (null? /tokens)
(set! host (car /tokens))
(set! db (cadr /tokens)))))))
(display "Senha: ")
(pg-repl:conn
(pg:connect
`((user . ,user)
(dbname . ,db)
(host . ,host)
(password . ,(with-stty '(not echo) read-line)))))
(current-input-port (make-gnu-readline-port))
(gnu-history-install-file-manager
(string-append (or (getenv "HOME") ".")
"/.csi.history"))
(newline)
(repl))

quinta-feira, 31 de julho de 2008

Copy & comment

Para quem seguidamente, como eu:

1. tem preguiça de criar uma revisão no VCS para alterar algo pequeno no código (só para ver se dá certo -- se não der, volta atrás rapidinho);

2. acha muito trabalhoso usar o editor de texto para duplicar um trecho de código (i.e., copiar & colar) e comentar uma das partes.

A função a seguir (em Elisp, para Emacs) faz as tarefas do item 2 para quem se enquadra no perfil do item 1:

(defun copy&comment (begin end)
(interactive "r")
(save-excursion
(copy-region-as-kill begin end)
(goto-char end)
(yank)
(comment-region begin end)))

terça-feira, 8 de julho de 2008

Interpretador de assembly em Scheme

Dando continuidade à série de programas inúteis que só servem para alimentar a procrastinação, a seguir estão a descrição e implementação (em Chicken Scheme) de um pequeno interpretador de uma linguagem assembly bem simples.

A linguagem possui apenas seis instruções e opera somente com números:

  • add <reg> <number | reg>: Soma um número ou o conteúdo do registrador usado como segundo argumento com o conteúdo do registrador usado como primeiro argumento. O resultado é armazenado no registrador usado como primeiro argumento.

  • mov <reg> <number | reg>: Armazena o número ou o conteúdo do registrador usado como segundo argumento no registrador usado como primeiro argumento.

  • lbl <label>: Associa um endereço de memória a um rótulo, o qual pode ser referenciado no programa pelas instruções jmp e jnz.

  • jmp <label>: Desvia o fluxo de execução para <label> (uma marca determinada através da instrução lbl).

  • jnz <reg> <label>: Desvia o fluxo de execução para <label> se o conteúdo do registrador usado como primeiro argumento for diferente de zero.

  • out <number | reg>: Imprime o número ou conteúdo do registrador usado como argumento.


A arquitetura hipotética considerada possui 8 registradores para leitura e escrita (r1 a r8), nenhum deles com função específica. Há também um registrador somente para leitura (ip) que armazena o endereço de memória da última instrução executada.

Por simplicidade, a sintaxe das instruções é semelhante à sintaxe de Scheme, ou seja, usa parênteses. Por exemplo:

(mov r1 3)

O código do interpretador está a seguir (tiny-assembly.scm):

;; Instrucoes:
;; add <reg> <number | reg>
;; mov <reg> <number | reg>
;; jmp <label>
;; jnz <reg> <label>
;; lbl <label>
;; out <number | reg>
;;
;; Registradores: r1..r8
;; Registrador "read-only": ip

(use srfi-1)

(define (run code)
(define memory (map cons (iota (length code)) code))
(define labels '())
(define registers
'((r1 . 0) (r2 . 0) (r3 . 0) (r4 . 0)
(r5 . 0) (r6 . 0) (r7 . 0) (r8 . 0)))
(define ip 0)
(define code-len (length code))

(define (die . msg)
(print "Error: "
(string-intersperse (map ->string msg) ""))
(exit 1))

(define (next-ip)
(set! ip (add1 ip)))

(define (reg-get register)
(alist-ref register registers))

(define (val-get thing)
(if (eq? thing 'ip)
ip
(if (number? thing)
thing
(reg-get thing))))

(define (reg-set! reg val)
(set! registers
(alist-update! reg (val-get val) registers)))

(define (label-address label)
(alist-ref label labels))

(define (add reg val)
(reg-set! reg (+ (reg-get reg) (val-get val))))

(define (mov reg val)
(reg-set! reg (val-get val)))

(define (jmp label)
(let ((address (or (reg-get label)
(label-address label))))
(if address
(set! ip address)
(die "label " label " not found."))))

(define (jnz reg label)
(if (zero? (reg-get reg))
(next-ip)
(jmp label)))

(define (lbl label)
(set! labels (alist-update! label ip labels)))

(define (finished? ip) (>= ip code-len))

(set! labels
(map (lambda (label)
(cons (caddr label) (car label)))
(filter (lambda (expr)
(eq? (cadr expr) 'lbl))
memory)))
(let loop ()
(if (finished? ip)
(exit)
(begin
(let* ((expr (alist-ref ip memory))
(op (car expr))
(arg1 (cadr expr))
(arg2 (and (not (null? (cddr expr)))
(caddr expr))))
(case op
((mov) (mov arg1 arg2))
((add) (add arg1 arg2))
((jmp) (jmp arg1))
((jnz) (jnz arg1 arg2))
((lbl) (noop))
((out) (print
(or (reg-get arg1)
(if (eq? arg1 'ip)
ip
arg1))))
(else (die op ": unknown command.")))
(unless (memq op '(jmp jnz))
(next-ip)))
(loop)))))

;;; Command line parser
(let ((args (command-line-arguments)))
(when (null? args)
(print "Usage: " (program-name) " <input-file>")
(exit 1))
(let ((file (car args)))
(unless (file-exists? file)
(print "Could not open " file)
(exit 1))
(run (handle-exceptions
exn
(die "parse error.")
(with-input-from-file file read-file)))))

A seguir estão alguns exemplos de código assembly e uso com o interpretador:

Multiplicação


A linguagem não possui uma instrução para multiplicação. Abaixo está a implementação de uma rotina para multiplicar dois números (7 x 4):

(mov r1 7)
(mov r2 4)
(mov r4 ip)
(jmp mul)
(jmp end)

;; Multiplicacao (x * y)
;; x -> r1
;; y -> r2
;; produto -> r3
;; endereco de retorno -> r4
(lbl mul)
(jnz r1 not-zero)
(jmp end)
(lbl not-zero)
(mov r3 0)
(lbl loopmul)
(add r3 r2)
(add r1 -1)
(jnz r1 loopmul)
(add r4 2)
(jmp r4)

(lbl end)
(out r3)

$ csi -s tiny-assembly.scm multiplicacao.asm
28

Fatorial


Toda e qualquer implementação de linguagem inútil deve mostrar uma implementação de fatorial como exemplo. Abaixo está a implementação usando o assembly descrito neste texto (where's your god now?!):

(mov r5 7) ;; entrada de dados

;; Fatorial
;; entrada -> r5
;; resultado -> r6
(lbl fatorial)
(mov r6 1)
(lbl fat-loop)
(jnz r5 fat-not-0)
(jmp end)
(lbl fat-not-0)
(add r5 -1)
(jnz r5 fat-not-1)
(jmp end)
(lbl fat-not-1)
(add r5 1)
(mov r1 r6)
(mov r2 r5)
(mov r4 ip)
(jmp mul)
(mov r6 r3)
(add r5 -1)
(jmp fat-loop)

;; Multiplicacao (x * y)
;; x -> r1
;; y -> r2
;; produto -> r3
;; endereco de retorno -> r4
(lbl mul)
(jnz r1 mul-not-0)
(jmp end)
(lbl mul-not-0)
(mov r3 0)
(lbl loopmul)
(add r3 r2)
(add r1 -1)
(jnz r1 loopmul)
(add r4 2)
(jmp r4)

(lbl end)
(out r6) ;; imprime o resultado

$ csi -s tiny-assembly.scm fatorial.asm
5040

quarta-feira, 2 de julho de 2008

Contagem de definições no toplevel

Dando início a uma série de programas para geração de estatísticas inúteis, abaixo está um pequeno código para contagem de definições feitas no toplevel (em Chicken Scheme).

(use srfi-1)

(define count-defines
(let* ((definers '(define define-macro define-constant
define-inline define-syntax))
(count-defines
(lambda (file)
(cons file
(length
(filter
(lambda (form)
(and (pair? form)
(memq (car form) definers)))
(with-input-from-file
file read-file)))))))
(lambda (files)
(let ((defines-count (map count-defines files)))
(for-each (lambda (file/defcount)
(print (car file/defcount) ": "
(cdr file/defcount)))
defines-count)
(print "Total: "
(reduce + 0 (map cdr defines-count)))))))

(let ((files (command-line-arguments)))
(if (null? files)
(exit 0)
(count-defines files)))

Exemplos de uso:


$ csi -s count-defines.scm count-defines.scm
count-defines.scm: 1
Total: 1


$ csi -s count-defines.scm spiffy/trunk/*.scm
spiffy/trunk/cgi-handler.scm: 5
spiffy/trunk/simple-directory-handler.scm: 4
spiffy/trunk/spiffy-base.scm: 70
spiffy/trunk/spiffy.scm: 1
spiffy/trunk/ssp-handler.scm: 10
spiffy/trunk/web-scheme-handler.scm: 4
Total: 94

Embora este programa não diga muita coisa de útil sobre o código que analisa, serve para mostrar um dos aspectos mais interessantes de Lisp: a possibilidade de se tratar, naturalmente, código como dados. Basicamente, a contagem de definições no toplevel consiste em ler todas as expressões de um arquivo e verificar se o car de cada expressão é um dos símbolos define, define-macro, define-constant, define-inline ou define-syntax (se a expressão for um par).

Este tipo de análise não é muito útil porque, pelo menos em Chicken, é possível especificar o que deve ser "visível" ou não no código compilado. Isto pode ser feito com as declarações export e hide. Outros motivos são que este programa não consegue inferir as definições de toplevel que serão geradas através da expansão de macros (web-scheme, por exemplo, usa esta estratégia) e que não computa definições feitas dentro de blocos cond-expand.

quinta-feira, 19 de junho de 2008

Acessando base de dados SQL com Scheme

Em alguns projetos em que estou trabalhando seguidamente tenho que acessar tabelas de bases de dados. Costumo usar o Postgres através do egg postgresql do sistema Chicken.

Para evitar de esquecer de fechar as conexões com o banco, normalmente uso um procedimento que recebe uma query como argumento. Este procedimento abre a conexão com o banco, executa a query e fecha a conexão automaticamente (o desempenho que se dane :-)). As credenciais do banco mantenho em um parâmetro (definido com make-parameter). O procedimento é algo como:

(define db-credentials (make-parameter '()))

(define (db-query query)
(let* ((db (pg:connect (db-credentials)))
(output (pg:query-tuples query db)))
(pg:close db)
output))

Mesmo com o uso do procedimento db-query, o acesso a colunas do banco não é das tarefas mais simples. Abaixo está um exemplo em que quero acessar as colunas username e email de uma tabela e associar o valor delas à variáveis em Scheme:

(db-credentials '((host . "localhost")
(user . "usuario")
(password . "****")
(dbname . "nome-da-base")))

(let* ((results
(let ((results
(db-query
"select username,email from users where user_id=1")))
(if (null? results)
#f
(car results))))
(username (and results (vector-ref results 0)))
(email (and results (vector-ref results 1))))
(print username)
(print email))

Como pode ser visto no exemplo, associar valores de colunas da base de dados a variáveis em Scheme é uma certa novela. Para facilitar esta tarefa, fiz o esquema mostrado abaixo:

(use postgresql)

(define db-map:credentials (make-parameter '()))

(define db-map:create-object
(let ()
(define (db-query query)
(let* ((db (pg:connect (db-map:credentials)))
(output (pg:query-tuples query db)))
(pg:close db)
output))
(lambda (query fields)
(let* ((query-results (let ((results (db-query query)))
(if (null? results)
#f
(car results)))))
(lambda (field)
(and query-results
(let ((pos (list-index (cut eq? <> field)
fields)))
(vector-ref query-results pos))))))))

O procedimento db-map:create-object recebe uma query SQL e uma lista de símbolos a serem associados com os valores das colunas obtidos como resultado da execução da query. db-map:create-object retorna um procedimento que recebe como argumento um símbolo representando uma coluna da base de dados e que retorna o valor associado ao símbolo.

Assim, para acessar o valor das colunas username e email, faço o seguinte:

(let ((obj (db-map:create-object
"select username,email from users where user_id=1"
'(username email))))
(print (obj 'username))
(print (obj 'email)))

A ordem dos símbolos da lista passada como segundo argumento deve ser a mesma dos valores das colunas resultantes da query SQL.

quinta-feira, 12 de junho de 2008

Persistência de dados (e código!) em Scheme

Hoje eu e o Vilson estávamos conversando sobre persistência de dados (e código!) em Lisp. Fiz um exemplo simples e estou colocando abaixo para não perder a viagem. :-)

O exemplo implementa um objeto mem (criado com o procedimento make-mem) e procedimentos para manipulação desse tipo de objeto: mem-get (para leitura de dados) e mem-set! (para escrita em memória e em disco).

A leitura de dados é sempre feita da memória (exceto na criação do objeto, que pode aproveitar dados do arquivo passado como argumento). As escritas são feitas em memória e em disco.

Os dados são armazenados em uma hash-table e indexados por símbolos.

Abaixo está a implementação simplificada (em Chicken Scheme), que usa o egg s11n:

(use s11n)

(define (make-mem file)
(cons file (if (file-exists? file)
(with-input-from-file
file
(cut deserialize))
(make-hash-table))))

(define (mem-get mem key #!optional default)
(hash-table-ref/default (cdr mem) key default))

(define (mem-set! mem key val)
(let ((file (car mem))
(data (cdr mem)))
(hash-table-set! data key val)
(with-output-to-file file (cut serialize data))))

A seguir está um exemplo que armazena uma lista e um procedimento (código!):

(let ((mem (make-mem "teste.data")))
(print (mem-get mem 'a))
(mem-set! mem 'a '(1 2 3))
(print (mem-get mem 'a))
(mem-set! mem 'soma (lambda (a b) (+ a b)))
(print ((mem-get mem 'soma) 2 2)))

O resultado da execução do código do exemplo é (caso em que teste.data inicialmente não existe):


#f
(1 2 3)
4

sexta-feira, 30 de maio de 2008

Minimização de poluição no espaço de nomes

Uma forma interessante de minimizar a poluição do espaço de nomes em Scheme é usando uma combinação de define, let e set!, tirando proveito das regras de escopo.

Supondo o seguinte caso: tenho um arquivo (uma biblioteca) que será usado por outras pessoas e embutido em outros códigos. Neste arquivo estarão alguns procedimentos que serão parte da API e procedimentos auxiliares úteis para o desenvolvimento do próprio código desta biblioteca. Mesmo sem usar um sistema de módulos, é possível minimizar a poluição do espaço de nomes que seria causado pelos procedimentos auxiliares com a seguinte estratégia:


(define proc-api-1 #f)
(define proc-api-2 #f)

(let ()
(define (proc-aux arg) (codigo))
(set! proc-api-1 (lambda () (proc-aux 3)))
(set! proc-api-2 (lambda () (proc-aux (proc-aux 1000)))))

No exemplo temos dois símbolos visíveis no escopo mais amplo (toplevel) deste arquivo : proc-api-1 e proc-api-2. A outra definição proc-aux, que é útil para a implementação dos procedimentos da API, fica no escopo do bloco (let () ...), ou seja, não é visível do toplevel. Dentro do bloco (let () ...), então, fazemos a atribuição do código dos procedimentos da API às variáveis definidas no toplevel.

Um exemplo mais prático (mas não menos besta):

(define ul #f)
(define li #f)
(define p #f)

(let ()

(define cria-tag
(lambda (nome)
(let ((nome (symbol->string nome)))
(lambda args
(string-append "<" nome ">"
(string-intersperse args "")
"</" nome ">"))))

(set! ul (cria-tag 'ul))
(set! li (cria-tag 'li))
(set! p (cria-tag 'p)))


Executando isso no REPL, podemos ver o efeito da estratégia usada:


$ csi -n

CHICKEN
(c)2008 The Chicken Team
(c)2000-2007 Felix L. Winkelmann
Version 3.2.0 - linux-unix-gnu-x86 [ manyargs dload ptables applyhook ]
SVN rev. 10664 compiled 2008-05-08 on ze-dureza (Linux)

#;1> (load "cria-tag.scm")
; loading cria-tag.scm ...
#;2> p
#<procedure (? . args)>
#;3> li
#<procedure (? . args)>
#;4> ul
#<procedure (? . args)>
#;5> cria-tag
Error: unbound variable: cria-tag
#;5> (p "nada")
"<p>nada</p>"

Conforme esperado, a definição de cria-tag não é visível no toplevel, mas as funções que a usam funcionam.

Esta estratégia é bastante usada na implementação do sistema Chicken, por exemplo.

sábado, 24 de maio de 2008

Giannini Thunder Sound Bass II

Neste final de semana meu pai me trouxe de presente um amplificador valvulado da década de 70, um Thunder Sound II, da Giannini. Um amplificador desses estava em uma das posições mais altas da minha lista de sonhos de consumo.

Inicialmente ele só ligava, as vávulas acendiam, mas o sinal da entrada não era amplificado. Havia apenas um zumbido no alto-falante da caixa em que liguei o amplificador.

Hoje decidi abri-lo para ver se descobria qual era o defeito. Para a minha sorte, era apenas um capacitor solto. Bastou soldá-lo de volta no lugar e o amplificador passou a funcionar. Os potenciômetros não estão lá grandes coisas (aceitável, dada a idade do troço), provavelmente serão trocados.

Fotos do amplificador em http://g3pd.ucpel.tche.br/~mario/fotos/thunder-sound

quarta-feira, 14 de maio de 2008

Geração de tablaturas em Scheme

O Pedro provavelmente vai me chamar de imbecil por eu estar apresentando um programa para geração de um tipo de notação musical tão primitiva, simplória e pouco representativa. Mas azar :-). Para analfabetos musicais como eu, tablaturas são uma forma simples de representar notas a serem tocadas em um instrumento.

O programa a seguir (em Chicken Scheme) gera tablaturas para contrabaixos de quatro cordas com duas oitavas por corda. Adaptar para guitarra/violão e para a notação de acordes não deve ser difícil.

(use (srfi 1))

(define (deriva-nota corda #!optional (desloc 0) oitava)
;; deriva notas a partir de corda e posicao de referencia
;; representando a nota mais grave
(let* ((corda-vazia (make-vector 4 '-))
(pos-corda-0 (+ desloc (if oitava 12 0)))
(pos (case corda
((0) pos-corda-0)
((1) (+ pos-corda-0 7))
((2) (+ pos-corda-0 2))
((3) (+ pos-corda-0 9)))))
(vector-set! corda-vazia corda
(if (and (>= pos 12) (not oitava))
(- pos 12)
pos))
(vector->list corda-vazia)))

(define-macro (cria-notas)
;; cria funcoes com nome correspondente `as notas
(let ((notas/desloc '((E . 0)
(F . 1)
(F# . 2)
(G . 3)
(G# . 4)
(Ab . 4)
(A . 5)
(A# . 6)
(Bb . 6)
(B . 7)
(C . 8)
(C# . 9)
(Db . 9)
(D . 10)
(D# . 11)
(Eb . 11))))
(append
'(begin)
(map (lambda (nota)
`(define (,(car nota) corda #!optional oitava)
(deriva-nota corda ,(cdr nota) oitava)))
notas/desloc))))

;; gera as definicoes das funcoes
(cria-notas)

(define (formata-corda corda notas)
(string-append
(case corda
((0) "G")
((1) "D")
((2) "A")
((3) "E"))
" |"
(string-intersperse
(map (lambda (nota)
(string-append
"--"
(->string nota)
(if (and (number? nota) (> nota 9))
"-"
"--")))
(map
(case corda
((0) cadddr)
((1) caddr)
((2) cadr)
((3) car))
notas))
"") "|"))

(define (tablatura . notas)
(define notas/corda 10) ;; numero maximo de notas por
;; linha da tablatura
(define (tab notas)
(for-each (lambda (corda)
(print (formata-corda corda notas)))
'(0 1 2 3)))
(let loop ((bloco notas))
(cond ((> (length bloco) notas/corda)
(tab (take bloco notas/corda))
(loop (drop bloco notas/corda)))
(else
(print "")
(tab (append ;; preenche ate' o final da linha
bloco
(make-list (- notas/corda (length bloco))
'(- - - -) )))))))

Para gerar uma tablatura, o usuário especifica as notas através de funções, e as passa como argumento para a função tablatura. As funções para representação de notas usam como argumentos a corda em que devem ser tocadas (de 0 a 3) e, opcionalmente, se uma oitava acima. Um exemplo com a escala pentatônica é mostrado abaixo:

(tablatura (G 0) (A# 0) (C 1) (D 1) (F 2))


G |--------------------------------------------------|
D |----------------------3---------------------------|
A |------------3----5--------------------------------|
E |--3----6------------------------------------------|

O programa quebra a linha da tablatura depois de um determinado número de notas (no código este parâmetro está com o valor 10) e completa a linha caso o número de notas seja menor que 10.

Uma tablatura com mais de 10 notas:

(tablatura
(G 0) (A# 0) (C 1) (D 1) (F 2)
(G 0) (A# 0) (C 1) (D 1) (F 2)
(G 0) (A# 0) (C 1) (D 1) (F 2))

G |--------------------------------------------------|
D |----------------------3------------------------3--|
A |------------3----5-------------------3----5-------|
E |--3----6-------------------3----6-----------------|

G |--------------------------------------------------|
D |----------------------3---------------------------|
A |------------3----5--------------------------------|
E |--3----6------------------------------------------|

terça-feira, 13 de maio de 2008

Programação para Web com Chicken e Ajax

Chicken possui uma extensão interessante para desenvolvimento para a Web: o egg ajax. Essa extensão é, na realidade, um wrapper para a biblioteca prototype.

O uso do egg ajax é bem fácil depois que se entende como ele funciona. Confesso que quebrei um pouco a cabeça para começar a usá-lo, e a dificuldade foi agravada pelo fato de eu não conhecer muito bem a biblioteca prototype na época.

Para facilitar o entendimento do egg ajax, vale a pena mencionar um recurso interessante do egg http: a possibilidade de definição de recursos (resources). Com isso, é possível associar uma URL a um procedimento que será executado no servidor. Esta colocação, embora pareça "solta" agora, é necessária para entender como o egg ajax funciona (espero que ela venha a fazer sentido até o final deste texto...).

Uma das funções mais práticas do egg ajax é a remote-link. Com ela, é possível realizar várias operações no cliente com base no resultado do procedimento executado no servidor. A seguir está um exemplo de aplicação básica de remote-link:

(remote-link
"Clique aqui"
(lambda ()
(print "Oi"))
update: "resultado")

remote-link recebe três argumentos:

  • O texto do link

  • Uma função sem argumentos a ser executada no servidor

  • O tipo de ação a ser executada no cliente e em qual elemento da página aplicar o resultado da função executada no servidor. O tipo de ação a ser executada é determinado pelo parâmetro de palavra-chave escolhido (update no exemplo) e o elemento a aplicar o resultado é a string associada ao parâmetro de palavra-chave (resultado no exemplo).


O exemplo com remote-link, então, gera os códigos HTML e Javascript necessários para atualizar o elemento de identificador resultado com o texto Oi quando o usuário clicar no link Clique aqui. O código gerado é mostrado abaixo:

<a href='#' onclick="new Ajax.Updater('resultado', '/9fa328569dc39078f13e29086bbf3b93', { });">Clique aqui</a>

O código Javascript gerado corresponde a criação de um objeto Updater da bilioteca prototype, cujos argumentos são o identificador do elemento a ser atualizado no cliente depois da execução do procedimento associado à URL usada como segundo argumento (/9fa328569dc39078f13e29086bbf3b93). Essa URL é gerada pelo egg ajax, usando a funcionalidade de recursos do egg http, mencionada no início deste texto. A URL fica associada à função usada como argumento para remote-link.

Vamos ver um exemplo prático com o servidor web Spiffy. São necessários os seguintes eggs:


Os eggs podem ser instalados da seguinte forma:

$ chicken-setup spiffy web-scheme ajax spiffy-utils

Todas as dependências serão instaladas automaticamente.

O programa para execução do servidor web está abaixo (web-server.scm):

(use spiffy web-scheme web-scheme-handler ajax spiffy-utils)

(spiffy-file-ext-handlers `(("ws" . ,web-scheme-handler)))
(spiffy-tcp-port 8080)
(spiffy-root-path ".")
(spiffy-debug-mode #t)
(start-server)

Para usar o egg ajax, é necessária a biblioteca prototype, que acompanha a extensão (o arquivo encontra-se em CHICKEN_PREFIX/lib/chicken/3/prototype.js, onde CHICKEN_PREFIX é o diretório onde o sistema Chicken foi instalado). Copie o arquivo prototype.js para o mesmo diretório onde foi criado o arquivo web-server.scm.

O arquivo para gerar a página HTML (index.ws) é mostrado abaixo:

(ws:page
(string-append
(div 'id "resultado")
(remote-link
"Clique aqui"
(lambda ()
(print "Oi"))
update: "resultado"))
additional-headers: (ajax))

Para ver o resultado, basta executar o servidor web:

$ csi -s web-server.scm

e acessar http://localhost:8080 no seu navegador preferido (algum que tenha suporte a Javascript).

A função ajax usada no exemplo gera o código HTML necessário para referenciar a biblioteca prototype na página.

Obviamente, é possível fazer algo de mais útil com isso. A seguir é mostrado um exemplo mais elaborado (tamanho.ws): uma aplicação que mostra em uma caixa do tipo select os arquivos no diretório raíz do servidor e exibe o tamanho quando são selecionados.

(use posix)

(ws:page
(string-append
(ws:select
'id "file-box"
'onchange (remote-action
(lambda ()
(let ((file (post-var "file")))
(print "Tamanho de " file " = "
(file-size file) " bytes")))
update: "resultado"
arguments: "'file=' + $F('file-box')")
(string-intersperse
(map (lambda (opt)
(option 'value opt opt))
(glob (make-pathname
(spiffy-root-path) "*")))
""))
(div 'id "resultado"))
additional-headers: (ajax))

O código acima monta uma caixa de opções (select de HTML) com os arquivos no diretório raíz do servidor (parâmetro (spiffy-root-path)) e usa remote-action para registrar a função que é executada no lado do servidor e executá-la quando um arquivo for selecionado na caixa de seleção.

O parâmetro de palavra-chave arguments especifica as variáveis a serem passadas através dos métodos GET e POST do HTTP. Se nenhum método for especificado, POST é usado (o parâmetro de palavra-chave method permite especificar o método a ser usado). A função $F em Javascript, da biblioteca prototype, retorna o conteúdo do widget cujo identificador é passado como argumento.

Para ver a aplicação em funcionamento, acesse http://localhost:8080/tamanho.ws no seu navegador

Mais detalhes sobre o funcionamento do egg ajax podem ser obtidos na documentação da extensão, em http://www.call-with-current-continuation.org/eggs/3/ajax.html.

segunda-feira, 12 de maio de 2008

Objetos em Scheme

Há uns dias o tópico "objetos" surgiu nas discussões no canal #lisp-br. A especificação de Scheme não oferece nenhum sistema de objetos para ser prontamente usado, mas a linguagem possui características que permitem a implementação de sistemas de objetos (tanto que há vários).

A seguir está um exemplo de implementação bem simplificada de um criador de objetos em Scheme (Chicken Scheme).

Considerações:

  • Objetos são representados por closures que aceitam mensagens para acesso a atributos internos.

  • Em Scheme, funções (procedures) são objetos de primeira classe, ou seja, podem ser manipuladas como qualquer outro tipo de valor (e.g., números, strings). Assim, uma "chamada de método" nesse sistema de objetos exemplo corresponde ao acesso a um atributo cujo valor é uma função.


(use (srfi 1))

(define (cria-ponto)
(letrec
((atributos
`((pos-x . 0)
(pos-y . 0)
(posicao . ,(lambda ()
(cons (this 'pos-x)
(this 'pos-y)
)))))
(this (lambda (var)
(alist-ref var atributos))))
(lambda args
(if (null? args)
(error "Nenhuma mensagem.")
(let ((acao (car args)))
(case acao
((set!) (let ((atributo (cadr args))
(valor (caddr args)))
(alist-update! atributo valor
atributos)
(void)))
((get) (let ((atributo (this (cadr args))))
(or atributo
(error "Atributo inexistente."))))
(else (error "Mensagem nao conhecida."))
))))))


A função cria-ponto retorna um objeto ponto, o qual é representado por uma função que aceita um número arbitrário de argumentos, que correspondem a mensagens. Os atributos do objeto são declarados na alist atributos, com um valor inicial. Um dos atributos (posicao) é uma função, que retorna um par representando as coordenadas do ponto.

A criação de um objeto ponto é feita com a avaliação da expressão (cria-ponto):

(define ponto (cria-ponto))

ponto, então, passa a representar um objeto ponto, o qual aceita mensagens. Alguns exemplos:

csi> (define ponto (cria-ponto))
csi> (ponto 'get 'pos-x)
0
csi> (ponto 'get 'pos-y)
0
csi> (ponto 'set! 'pos-y 10)
csi> (ponto 'get 'pos-y)
10
csi> (ponto 'get 'posicao)
#<procedure (?)>
csi> ((ponto 'get 'posicao))
(0 . 10)

Algumas mensagens causam alteração nos objetos (i.e., set!). Como a representação de pontos é feita com closures, a alteração do estado interno de um objeto não deve alterar o dos outros:

csi> (define outro-ponto (cria-ponto))
csi> (outro-ponto 'get 'pos-x)
0
csi> (outro-ponto 'set! 'pos-x 15)
csi> ((outro-ponto 'get 'posicao))
(15 . 0)
csi> ((ponto 'get 'posicao))
(0 . 10)

A implementação de introspecção pode ser feita com a implementação tratadores para duas novas mensagens: tipo e atributos, ou seja, fazemos com que objetos ponto respondam a mensagens tipo e atributos:

(use (srfi 1))

(define (cria-ponto)
(letrec
((tipo 'ponto)
(atributos
`((pos-x . 0)
(pos-y . 0)
(posicao . ,(lambda ()
(cons (this 'pos-x)
(this 'pos-y)
)))))
(this (lambda (var)
(alist-ref var atributos))))
(lambda args
(if (null? args)
(error "Nenhuma mensagem.")
(let ((acao (car args)))
(case acao
((set!) (let ((atributo (cadr args))
(valor (caddr args)))
(alist-update! atributo valor
atributos)
(void)))
((get) (let ((atributo (this (cadr args))))
(or atributo
(error "Atributo inexistente."))))
((tipo) tipo)
((atributos) atributos)
(else (error "Mensagem nao conhecida."))
))))))




Para obter as informações de tipo e atributos do objeto ponto, então:

csi> (define ponto (cria-ponto))
csi> (ponto 'tipo)
ponto
csi> (ponto 'atributos)
((pos-x . 0) (pos-y . 0) (posicao . #<procedure (?)>))

Para a implementação de um simples mecanismo de herança, fazemos com que a função criadora de objetos passe a aceitar uma lista de ancestrais, e incorpore a lista de atributos dos ancestrais aos atributos do objeto sendo criado. O exemplo a seguir mostra a criação de um objeto representando um ponto em três dimensões, aproveitando características de um ponto em duas dimensões (pos-x e pos-y):

(use (srfi 1))

(define (cria-ponto-3D . pais)
(letrec
((tipo 'ponto-3D)
(tipos (cons tipo
(map (lambda (pai)
(pai 'tipo))
pais)))
(supers (apply append
(map (lambda (pai)
(pai 'atributos))
pais)))
(atributos
`((pos-z . 0)
(posicao . ,(lambda ()
(list (this 'pos-x)
(this 'pos-y)
(this 'pos-z)
)))))
(this (lambda (var)
(or (alist-ref var atributos)
(alist-ref var supers)))))
(lambda args
(if (null? args)
(error "Nenhuma mensagem.")
(let ((acao (car args)))
(case acao
((set!) (let ((atrib (cadr args))
(valor (caddr args)))
(if (alist-ref atrib atributos)
(alist-update! atrib valor
atributos)
(alist-update! atrib valor
supers))
(void)))
((get) (let ((atributo (this (cadr args))))
(or atributo
(error "Atributo inexistente."))))
((tipo) tipo)
((atributos) (append supers atributos))
((is-a?) (lambda (t)
(not (not (memq t tipos)))))
(else (error "Mensagem nao conhecida."))
))))))

O objeto ponto 3D incorpora as características de seus ancestrais e adiciona uma nova: pos-z, além de responder mensagens novas, como is-a?.

csi> (define ponto-3D (cria-ponto-3D ponto))
csi> (ponto-3D 'tipo)
ponto-3D
csi> ((ponto-3D 'is-a?) 'ponto)
#t
csi> ((ponto-3D 'is-a?) 'ponto-3D)
#t
csi> ((ponto-3D 'is-a?) 'ponto-4D)
#f

É possível generalizar a função criadora de objetos para que ela receba como argumentos o tipo, os atributos e os ancestrais dos objetos a serem criados:

(use (srfi 1))

(define-macro (cria tipo atributos pais)
`(letrec
((tipo ,tipo)
(tipos (cons ,tipo
(map (lambda (pai)
(pai 'tipo))
,pais)))
(supers (apply append
(map (lambda (pai)
(alist-copy (pai 'atributos)))
,pais)))
(atributos ,atributos)
(this (lambda (var)
(or (alist-ref var atributos)
(alist-ref var supers)))))
(lambda args
(if (null? args)
(error "Nenhuma mensagem.")
(let ((acao (car args)))
(case acao
((set!) (let ((atrib (cadr args))
(valor (caddr args)))
(if (alist-ref atrib atributos)
(alist-update! atrib valor
atributos)
(alist-update! atrib valor
supers))
(void)))
((get) (let ((atributo (this (cadr args))))
(or atributo
(error "Atributo inexistente."))))
((tipo) tipo)
((atributos) (append supers atributos))
((is-a?) (lambda (t)
(not (not (memq t tipos)))))
(else (error "Mensagem nao conhecida."))
))))))

csi> (define obj (cria 'ponto
---> `((pos-x . 0)
---> (pos-y . 0)
---> (posicao . ,(lambda ()
---> (cons (this 'pos-x)
---> (this 'pos-y)))))
---> '()))
csi>
csi> (obj 'get 'pos-x)
0
csi> (obj 'set! 'pos-x 30)
csi> (obj 'get 'pos-x)
30
csi> (obj 'tipo)
ponto
csi> ((obj 'is-a?) 'ponto)
#t
csi> ((obj 'is-a?) 'ponto00)
#f
csi>
csi> (define obj2 (cria 'ponto-3D
---> `((pos-z . 0)
---> (posicao . ,(lambda ()
---> (list (this 'pos-x)
---> (this 'pos-y)
---> (this 'pos-z)))))
---> (list obj)))
csi>
csi> ((obj2 'is-a?) 'ponto)
#t
csi> (obj2 'get 'pos-x)
30
csi> (obj2 'set! 'pos-x 90)
csi> (obj 'get 'pos-x)
30
csi> (obj2 'get 'pos-x)
90
csi> ((obj2 'get 'posicao))
(90 0 0)

O sistema de objetos mostrado aqui não utiliza classes para a representação de características de objetos. As características de objetos são representadas em uma função geradora de objetos. A herança utiliza objetos para a representação de ancestrais, não classes, havendo a clonagem dos objetos usados como ancestrais. A ordem para resolução de atributos é a ordem de passagem de ancestrais na criação de objetos, ou seja, se dois ancestrais possuem um atributo comum, o atributo do primeiro antecestral será o usado pelo objeto filho.