Doctests em Scheme

3 views
Skip to first unread message

Luciano Ramalho

unread,
Jul 9, 2009, 11:00:57 PM7/9/09
to ei...@googlegroups.com
Hoje eu comecei a fazer os exercícios do livro e senti falta de ter em
Scheme um arcabouço de testes automatizados bem simples que existe em
Python, chamado doctest.

Para quem não conhece, um doctest é basicamente uma longa string em
Python que reproduz as entradas e saídas uma sessão interativa que
demonstra a funcionalidade uma API. Elem de ser um mecanismo de testes
automatizados, o doctest serve para assegurar que exemplos embutidos
na documentação são válidos.

Como Lisp sempre teve seu REPL, eu imagino que alguém alguma vez já
teve essa idéia no mundo Lisp. Alguém conhece uma implementação desse
tipo de teste em Lisp ou Scheme?

Ultimamente tenho usado doctests não só como programador mas também
como instrutor: apresento o mecanismo para os alunos logo na primeira
aula, e a partir daí eu uso este formato para os exercícios. Vejam
dois exemplos de exercícios feitos na base de doctest:

http://bitbucket.org/ramalho/propython/src/tip/fundamentos/exercicios/sequencias.py
http://bit.ly/kGfJh

http://bitbucket.org/ramalho/propython/src/tip/fundamentos/exercicios/fun_dict.py
http://bit.ly/cWvox

[ ]s
Luciano

Mario Domenech Goulart

unread,
Jul 10, 2009, 7:27:19 AM7/10/09
to ei...@googlegroups.com
Alô Luciano.

On Fri, 10 Jul 2009 00:00:57 -0300 Luciano Ramalho <luc...@ramalho.org> wrote:

> Hoje eu comecei a fazer os exercícios do livro e senti falta de ter em
> Scheme um arcabouço de testes automatizados bem simples que existe em
> Python, chamado doctest.
>
> Para quem não conhece, um doctest é basicamente uma longa string em
> Python que reproduz as entradas e saídas uma sessão interativa que
> demonstra a funcionalidade uma API. Elem de ser um mecanismo de testes
> automatizados, o doctest serve para assegurar que exemplos embutidos
> na documentação são válidos.

Se entendi direito como funciona o doctest de Python, a implementação em
Scheme ficaria um pouco confusa.

Exemplo:

(define (proc)
"(+ 3 4)
> 7
")

A string no corpo de `proc' é o teste ou o que será produzido por `proc'
quando for avaliado?

Um abraço.
Mario

Carlos da Silva Santos

unread,
Jul 10, 2009, 8:42:41 AM7/10/09
to ei...@googlegroups.com
2009/7/10 Mario Domenech Goulart <mario....@gmail.com>:
A convenção do doctest em python é que linhas que começam com ">>>"
fazem parte do código do teste, as linhas que não têm tal prefixo
correspondem à saída do teste (aquilo que é imprimido pelo
interpretador). O doctest foi pensando de modo que você possa copiar
uma sequência de comandos/saídas do interpretador diretamente para o
teste. Se eu fosse fazer algo análogo em Scheme, minha preocupação
seria manter essa agilidade.

[]s
Carlos


> Um abraço.
> Mario
>
> >
>

Luciano Ramalho

unread,
Jul 10, 2009, 12:53:58 PM7/10/09
to ei...@googlegroups.com
2009/7/10 Mario Domenech Goulart <mario....@gmail.com>:
> Se entendi direito como funciona o doctest de Python, a implementação em
> Scheme ficaria um pouco confusa.
>
> Exemplo:
>
>  (define (proc)
>     "(+ 3 4)
>      > 7
>     ")
>
> A string no corpo de `proc' é o teste ou o que será produzido por `proc'
> quando for avaliado?

Vamos por enquanto deixar de lado como o doctest se integra ao
código-fonte. Essa questão não é essencial porque também é possível
executar doctests em um arquivo de texto arbitrário, que não é um
arquivo .py válido. Ou seja, um arquivo que é do começo ao fim um log
de interações.

O que é essencial é o mecanismo de analisar uma string onde algumas
linhas começam com um sinal especial (tipo ">", que é o prompt do REPL
do DrScheme, por exemplo). Cada linha de entrada marcada com ">" é
avaliada como se fosse digitada no prompt do REPL, e o resultado da
avaliação é comparado com o conteúdo da(s) proxima(s) linha(s) do
doctest.

Para dar um exemplo concreto, um doctest equivalente ao exercício 1.1
poderia ser assim:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
> 10
10
> (+ 5 3 4)
12
> (- 9 1)
8
> (/ 6 2)
3
> (+ (* 2 4) (- 4 6))
6
> (define a 3)
> (define b (+ a 1)
> (+ a b (* a b))
19
> (= a b)
#f
> (if (and (> b a) (< b (* a b)))
b
a) ; multiline sexps are OK, result follows last right-paren
4
> (cond ((= a 4) 6)
((= b 4) (+ 6 7 a))
(else 25))
16
> (+ 2 (if (> b a) b a))
6
> (* (cond ((> a b) a)
((< a b) b)
(else -1))
(+ a 1))
16
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

[ ]s
Luciano

Kao Cardoso Felix

unread,
Jul 10, 2009, 1:07:01 PM7/10/09
to ei...@googlegroups.com
E se fizesse algo do tipo

(:> (foo bar baz)
result)

?

Fica um pouquinho menos limpo, mas dá pra implementar direto no
scheme, não? Pelo pouco que eu sei o T> teria q ser uma macro ou então
a expressão avaliada teria que ser passada como um quote, i.e. '(foo
bar baz).

Bom, escolhi o :> só pq o > já existe, mas poderia ser outro. Uma
implementação tosquíssima usando quote pra passar a expressão seria
assim:

(define (:> expr result)
(if (equal? (eval expr) result)
(print "OK")
(error "Test failed!")))

Testando no cli:

#;3> (:> '(+ 4 5) 9)
OK

#;4> (:> '(+ 4 5) 8)

Error: Test failed!

Call history:

<syntax> (:> (quote (+ 4 5)) 8)
<syntax> (quote (+ 4 5))
<eval> (:> (quote (+ 4 5)) 8)
<eval> [:>] (equal? (eval expr) result)
<eval> [:>] (eval expr)
<syntax> (+ 4 5)
<eval> (+ 4 5)
<eval> [:>] (error "Test failed!") <--

--
Kao Cardoso Félix

Página pessoal: http://www.inf.ufrgs.br/~kcfelix
Blog: http://kaofelix.blogspot.com

Kao Cardoso Felix

unread,
Jul 10, 2009, 1:09:59 PM7/10/09
to ei...@googlegroups.com
Acabei de ver q uns nomes mais bonitinhos tipo >> ou >>> estão desocupados tb :)

Mario Domenech Goulart

unread,
Jul 10, 2009, 1:15:54 PM7/10/09
to ei...@googlegroups.com
Alô Kao

On Fri, 10 Jul 2009 14:07:01 -0300 Kao Cardoso Felix <kcf...@gmail.com> wrote:

> E se fizesse algo do tipo
>
> (:> (foo bar baz)
> result)
>
> ?
>
> Fica um pouquinho menos limpo, mas dá pra implementar direto no
> scheme, não? Pelo pouco que eu sei o T> teria q ser uma macro ou então
> a expressão avaliada teria que ser passada como um quote, i.e. '(foo
> bar baz).

Algo semelhante a isso já existe (é o que costumo usar):
http://chicken.wiki.br/eggref/4/test

Um abraço.
Mario

Kao Cardoso Felix

unread,
Jul 10, 2009, 1:24:18 PM7/10/09
to ei...@googlegroups.com
2009/7/10 Mario Domenech Goulart <mario....@gmail.com>:
> Algo semelhante a isso já existe (é o que costumo usar):
> http://chicken.wiki.br/eggref/4/test

Duvidava mesmo que minha idéia fosse super original :)
Eu acho legal essa sintaxe pra criar testes. Talvez não seja tão sexy
qto os doctests que realmente parecem exemplos de uso, mas já é bem
limpa.

Bom, e tendo essas macros de teste disponíveis é só questão de fazer
um parsing da sintaxe sugerida pelo Ramalho e criar testes como esse,
não? Parece razoavelmente fácil, mas meu scheme-fu ainda é mto baixo
pra tentar algo assim...

Tentei ver tb se o scheme tinha docstrings, mas pelas minhas buscas
rápidas parece q não... Alguém confirma?

Luciano Ramalho

unread,
Jul 10, 2009, 1:24:44 PM7/10/09
to ei...@googlegroups.com
2009/7/10 Mario Domenech Goulart <mario....@gmail.com>:
> Algo semelhante a isso já existe (é o que costumo usar):
> http://chicken.wiki.br/eggref/4/test

Eu também tenho usado algo semelhante no PLT-Scheme (linguagem PLAI),
veja este código:

http://code.google.com/p/eipc/source/browse/pessoal/lr/exerc/exc_1_1.scm

Este outro é um exemplo de uso mais típico:

http://code.google.com/p/eipc/source/browse/pessoal/lr/exerc/exc_1_3.scm

Mas acho que valeria a pena o esforço de tirar a sintaxe adicional
para deixar a aparência do teste idêntica ao log de uma interação com
o REPL, ou se não idêndica, mais próxima. Usar >> seria OK, por
exemplo.

[ ]s
Luciano

Luciano Ramalho

unread,
Jul 10, 2009, 1:30:35 PM7/10/09
to ei...@googlegroups.com
2009/7/10 Kao Cardoso Felix <kcf...@gmail.com>:

> Tentei ver tb se o scheme tinha docstrings, mas pelas minhas buscas
> rápidas parece q não... Alguém confirma?

Eu também fiz umas buscas rápidas e não achei.

Mas achei esta página [1] que dá uma forte pista de que pelo menos no
início de 2008 não existia algo semelhante.

[1] http://4.flowsnake.org/archives/45

Para quem não conhece, o Ian Bicking, que sugere "Write doctest for
Scheme!" é um programador extremamente prolífico e influente na
comunidade Python internacional. Ele diz que lá que insiste nisso
frequentemente, e até explica porque seria muito mais fácil fazer
doctests funcionar em Scheme do que em Python.

[ ]s
Luciano

Mario Domenech Goulart

unread,
Jul 10, 2009, 1:37:15 PM7/10/09
to ei...@googlegroups.com
On Fri, 10 Jul 2009 14:24:18 -0300 Kao Cardoso Felix <kcf...@gmail.com> wrote:

> 2009/7/10 Mario Domenech Goulart <mario....@gmail.com>:
>> Algo semelhante a isso já existe (é o que costumo usar):
>> http://chicken.wiki.br/eggref/4/test
>
> Duvidava mesmo que minha idéia fosse super original :)
> Eu acho legal essa sintaxe pra criar testes. Talvez não seja tão sexy
> qto os doctests que realmente parecem exemplos de uso, mas já é bem
> limpa.

Há também http://chicken.wiki.br/eggref/3/test-infrastructure


> Bom, e tendo essas macros de teste disponíveis é só questão de fazer
> um parsing da sintaxe sugerida pelo Ramalho e criar testes como esse,
> não? Parece razoavelmente fácil, mas meu scheme-fu ainda é mto baixo
> pra tentar algo assim...
>
> Tentei ver tb se o scheme tinha docstrings, mas pelas minhas buscas
> rápidas parece q não... Alguém confirma?

Para Scheme "puro" não existe mas, obviamente, há hacks não muito
bonitos para isso (exemplo ao final de
http://chicken.wiki.br/eggref/3/procedure-decoration).

Assim como no caso de doctests, docstrings seriam ambíguas.

(define (proc)
"Isto eh a string que representa o valor
da avaliacao de proc ou uma docstring?")

Em http://www.cs.aau.dk/~normark/schemedoc/ há uma implementação que
retira documentação de comentários.

Um abraço.
Mario

Kao Cardoso Felix

unread,
Jul 10, 2009, 1:47:46 PM7/10/09
to ei...@googlegroups.com
2009/7/10 Mario Domenech Goulart <mario....@gmail.com>:
>  (define (proc)
>    "Isto eh a string que representa o valor
>     da avaliacao de proc ou uma docstring?")

Só por curiosidade testei no emacs lisp que tem docstring e ele trata
como se fosse o valor de avaliação:
(defun foo ()
"bar")
(foo)
-> "bar"

Já nesse caso ele entende que a primeira string é a docstring:
(defun foo ()
"bar"
"baz")
(foo)
-> "baz"

Luciano Ramalho

unread,
Jul 10, 2009, 1:50:56 PM7/10/09
to ei...@googlegroups.com
2009/7/10 Mario Domenech Goulart <mario....@gmail.com>:
> Para Scheme "puro" não existe mas, obviamente, há hacks não muito
> bonitos para isso (exemplo ao final de
> http://chicken.wiki.br/eggref/3/procedure-decoration).
>
> Assim como no caso de doctests, docstrings seriam ambíguas.
>
>  (define (proc)
>    "Isto eh a string que representa o valor
>     da avaliacao de proc ou uma docstring?")

Não existe essa ambiguidade no doctest em Python, e não precisaria
existir no doctest em Scheme, Mario. Acho que você não entendeu que a
docstring é primariamente texto para humanos, e não representa
qualquer tipo de interação com a linguagem de programação. Agora,
dentro da docstring podem existir trechos que seguem uma sintaxe
específica. A sintaxe é que quando uma linha começa com o marcador >>>
então ela pode ser avaliada como se fosse uma interação com no REPL, e
quando ela é avaliada o resultado é comparado com as linhas seguintes,
até o próximo >>> ou até o final do trecho de igual endentação. Vamos
deixar de lado por hora a questão da endentação. Caso hipoteticamente
existissem doctests embutidos em código Scheme, eles poderiam ser
assim:

(define (proc)
"Este proc elimina a fome no mundo. Veja como se usa:
> (proc)
451415256 pratos de feijoada distribuidos
"
(corpo-do-proc)
)

Para entender melhor, dá uma lida neste resumo que eu fiz para os meus alunos:

http://bitbucket.org/ramalho/propython/src/tip/fundamentos/exercicios/intro_doctest.py
ou http://bit.ly/RAPjS

Porém, eu volto a frisar: nem acho que seja uma característica
essencial dos doctests eles serem embutidos no código-fonte, pois
muitas vezes em grandes projetos Python (como o Zope) muitos se não a
maioria dos doctests ficam em arquivos à parte, que são doctest do
início ao fim, e que são lidos por um test-runner especial.

Então eu não me importaria de fazer

(doctest "meus-testes.txt")

para executar os testes do arquivo meus-testes.txt e gerar um relatório.

[ ]s
Luciano

Mario Domenech Goulart

unread,
Jul 10, 2009, 2:10:04 PM7/10/09
to ei...@googlegroups.com
Alô Luciano.

On Fri, 10 Jul 2009 14:50:56 -0300 Luciano Ramalho <ram...@gmail.com> wrote:

> 2009/7/10 Mario Domenech Goulart <mario....@gmail.com>:
>> Para Scheme "puro" não existe mas, obviamente, há hacks não muito
>> bonitos para isso (exemplo ao final de
>> http://chicken.wiki.br/eggref/3/procedure-decoration).
>>
>> Assim como no caso de doctests, docstrings seriam ambíguas.
>>
>>  (define (proc)
>>    "Isto eh a string que representa o valor
>>     da avaliacao de proc ou uma docstring?")
>
> Não existe essa ambiguidade no doctest em Python, e não precisaria
> existir no doctest em Scheme, Mario.

Em Python realmente não existe ambiguidade, pois Python exige um
`return' explícito para que uma função retorne um valor. Em Scheme não
há return explícito (*), então, se o primeiro valor do corpo de um
procedimento também for o último, ele será o resultado da avaliação.
Neste caso, não há como distiguir entre uma docstring e algo relevante
para o código "executável" do programa.

(*) a menos que se use um procedimento de escape de uma continuação --
mas isso é outra história (exemplo em
http://call-with-hopeless-continuation.blogspot.com/2009/03/definicao-de-procedimentos-e-return.html)

Exemplo:

== Python

>>> def proc():
... "abc"
...
>>> proc()
>>>


== Scheme

> (define (proc)
---> "abc")
> (proc)
"abc"
>


Um abraço.
Mario

Luciano Ramalho

unread,
Jul 10, 2009, 2:22:42 PM7/10/09
to ei...@googlegroups.com
2009/7/10 Mario Domenech Goulart <mario....@gmail.com>:
> Em Python realmente não existe ambiguidade, pois Python exige um
> `return' explícito para que uma função retorne um valor.  Em Scheme não
> há return explícito (*), então, se o primeiro valor do corpo de um
> procedimento também for o último, ele será o resultado da avaliação.
> Neste caso, não há como distiguir entre uma docstring e algo relevante
> para o código "executável" do programa.

Ok, mas vamos combinar que você está sendo purista. Se a o primeiro
valor no corpo do procedimento é também o último, e ainda por cima
este único valor que existe no corpo do procedimento é uma string,
então na prática este procedimento trivial cuja função é devolver uma
string constante não precisa realmente ser testado...

Agora, eu sinceramente duvido que existam muitas ocorrências de
procedimentos assim no corpus de código-fonte Lisp acumulado ao longo
dos últimos 50 anos, mas sou novato então posso estar enganado...

E de qualquer forma, é como eu venho dizendo, mesmo que não seja
implementada uma sintaxe para embutir docstrings com doctests em
código Scheme, ainda assim eu acho que seria bastante útil ter uma
maneira de executar uma série de doctests em um arquivo à parte, como
no exemplo que eu dei na minha mensagem anterior.

[ ]s
Luciano

Mario Domenech Goulart

unread,
Jul 10, 2009, 2:54:21 PM7/10/09
to ei...@googlegroups.com
Alô Luciano.

On Fri, 10 Jul 2009 15:22:42 -0300 Luciano Ramalho <ram...@gmail.com> wrote:

> 2009/7/10 Mario Domenech Goulart <mario....@gmail.com>:
>> Em Python realmente não existe ambiguidade, pois Python exige um
>> `return' explícito para que uma função retorne um valor.  Em Scheme não
>> há return explícito (*), então, se o primeiro valor do corpo de um
>> procedimento também for o último, ele será o resultado da avaliação.
>> Neste caso, não há como distiguir entre uma docstring e algo relevante
>> para o código "executável" do programa.
>
> Ok, mas vamos combinar que você está sendo purista. Se a o primeiro
> valor no corpo do procedimento é também o último, e ainda por cima
> este único valor que existe no corpo do procedimento é uma string,
> então na prática este procedimento trivial cuja função é devolver uma
> string constante não precisa realmente ser testado...

Sim, é um certo purismo. Mas como esse grupo tem cunho didático, achei
que seria adequado explicar os porquês da coisa. Não significa,
necessariamente, que eu seja purista e que ache que docstrings e
doctests não devam existir.


> Agora, eu sinceramente duvido que existam muitas ocorrências de
> procedimentos assim no corpus de código-fonte Lisp acumulado ao longo
> dos últimos 50 anos, mas sou novato então posso estar enganado...

Não tenho dados sobre isso também, mas imagino casos de uso. Em Scheme
é muito comum o uso de procedimentos como argumentos de
procedimentos. Tanto que existem procedimentos como o `identity'
(http://chicken.wiki.br/man/4/Unit%20data-structures#identity), que à
primeira vista podem parecer inúteis, mas são usados na
prática. Normalmente essa estratégia é usada para prover mais
flexibilidade a quem for usar os procedimentos (em vez de fixar o tipo
de um parâmetro como string, faz-se com que ele seja um procedimento, o
qual pode retornar, dentre outras coisas, uma string). Assim, eu não me
espantaria por ver um procedimento que simplesmente retorne uma
string. Se a string é uma docstring, uma teststring, uma string
relevante para o program ou se o procedimento precisa ou não ser
testado, são outras questões.

Um abraço.
Mario

Luciano Ramalho

unread,
Jul 10, 2009, 5:31:55 PM7/10/09
to ei...@googlegroups.com
2009/7/10 Mario Domenech Goulart <mario....@gmail.com>:
> Sim, é um certo purismo.  Mas como esse grupo tem cunho didático, achei
> que seria adequado explicar os porquês da coisa.  Não significa,
> necessariamente, que eu seja purista e que ache que docstrings e
> doctests não devam existir.

OK, Mario, concordo que o esclarecimento que você fez foi importante.

Perceba que a minha "campanha" é a favor de doctests, uma sintaxe para
escrever testes imitando uma interação com o REPL, e não por
docstrings, que são um jeito de embutir documentação (com ou sem
doctests) no código-fonte. São coisas diferentes, e eu reconheço que a
implementação de docstrings em Scheme é mais complicada e talvez nem
valha a pena, embora aparentemente já existem esforços neste sentido,
pelo que entendi desta conversa. Agora doctests eu acredito que têm
uma relação custo-benefício muito favorável em Scheme (ou qualquer
outra linguagem que tenha um REPL). Se for possível combinar doctests
e docstrings ótimo, mas não é necessário ir tão longe para termos os
benefícios de doctests.

>> Agora, eu sinceramente duvido que existam muitas ocorrências de
>> procedimentos assim no corpus de código-fonte Lisp acumulado ao longo
>> dos últimos 50 anos, mas sou novato então posso estar enganado...
>
> Não tenho dados sobre isso também, mas imagino casos de uso.  Em Scheme
> é muito comum o uso de procedimentos como argumentos de
> procedimentos. Tanto que existem procedimentos como o `identity'
> (http://chicken.wiki.br/man/4/Unit%20data-structures#identity), que à
> primeira vista podem parecer inúteis, mas são usados na
> prática. Normalmente essa estratégia é usada para prover mais
> flexibilidade a quem for usar os procedimentos (em vez de fixar o tipo
> de um parâmetro como string, faz-se com que ele seja um procedimento, o
> qual pode retornar, dentre outras coisas, uma string). Assim, eu não me
> espantaria por ver um procedimento que simplesmente retorne uma
> string. Se a string é uma docstring, uma teststring, uma string
> relevante para o program ou se o procedimento precisa ou não ser
> testado, são outras questões.

Legal, muito grato pela aula Mario. A partir do momento que você
entrou no grupo (e depois outros schemers), eu sabia aprenderíamos
muito com você(s)!

[ ]s
Luciano

Mario Domenech Goulart

unread,
Jul 10, 2009, 8:06:56 PM7/10/09
to ei...@googlegroups.com
Alô Luciano.

On Fri, 10 Jul 2009 18:31:55 -0300 Luciano Ramalho <ram...@gmail.com> wrote:

> Perceba que a minha "campanha" é a favor de doctests, uma sintaxe para
> escrever testes imitando uma interação com o REPL

Coloquei em [1] uma implementação "prova de conceito" e cheia de
limitações desse esquema de doctests (em Chicken Scheme).

Um exemplo de uso está em [2]. A saída da execução de [2] está em [3].

A sintaxe das strings de teste é a seguinte:

* e expressão sob teste, que seria digitada no REPL, deve ser
precedida por `>'

* o resultado esperado deve ser precedido por `:'

O parser das strings de teste é bastante limitado. Não são admitidas
múltiplas linhas para expressões de teste nem de resultado.

O parser ignora linhas não iniciadas por `>' ou `:'.

A única forma sintática para declaração de procedimentos admitida é:

(define (proc args)
body)

Outras formas, como as abaixo, NÃO são suportadas:

(define proc
(lambda (args)
body))

(define proc #f)
(set! proc (lambda (args) body))

(define proc
(let ()
(lambda (args)
body)))

Teste de procedimentos resultantes da expansão de macros também não é
suportado.

Sobre a implementação: basicamente, é um procedimento (`doctest') que
lê todas as formas (forms) do arquivo em que é invocado e procura por
definições com o padrão

(define (proc args)
"doctest"
body)

Então a string com os testes é extraída da definição e é passada para
o parser de strings de teste, o qual avalia as expressões e os
resultados esperados e imprime o resultado.

Um aspecto interessante da implementação é a forma como o parsing do
código é feito: com manipulação de listas (estrutura de dados usada
para representar código em Scheme).

O código, naturalmente, não está muito organizado, pois foi feito à
moda louco, sem muito planejamento.

[1] http://paginas.ucpel.tche.br/~mario/misc/doctest/doctest.scm
[2] http://paginas.ucpel.tche.br/~mario/misc/doctest/testdoctest.scm
[3] http://paginas.ucpel.tche.br/~mario/misc/doctest/test-out.txt

Um abraço.
Mario

Guaracy Monteiro

unread,
Jul 11, 2009, 2:39:54 AM7/11/09
to ei...@googlegroups.com
>
> Hoje eu comecei a fazer os exercícios do livro e senti falta de ter em
> Scheme um arcabouço de testes automatizados bem simples que existe em
> Python, chamado doctest.

O que é Python? Uma cobra? :-)

>
> Para quem não conhece, um doctest é basicamente uma longa string em
> Python que reproduz as entradas e saídas uma sessão interativa que
> demonstra a funcionalidade uma API. Elem de ser um mecanismo de testes
> automatizados, o doctest serve para assegurar que exemplos embutidos
> na documentação são válidos.
>
> Como Lisp sempre teve seu REPL, eu imagino que alguém alguma vez já
> teve essa idéia no mundo Lisp. Alguém conhece uma implementação desse
> tipo de teste em Lisp ou Scheme?
>
> Ultimamente tenho usado doctests não só como programador mas também
> como instrutor: apresento o mecanismo para os alunos logo na primeira
> aula, e a partir daí eu uso este formato para os exercícios. Vejam
> dois exemplos de exercícios feitos na base de doctest:
>
> http://bitbucket.org/ramalho/propython/src/tip/fundamentos/exercicios/sequencias.py
> http://bit.ly/kGfJh
>
> http://bitbucket.org/ramalho/propython/src/tip/fundamentos/exercicios/fun_dict.py
> http://bit.ly/cWvox
>

Não achei muito interessante mas achei legal pois serve como prova
automática. O professor só precisa executar o programa para dar a nota
para o aluno. Serviria para o exercício 1.1, mas mesmo assim eu
prefiro digitar o código para pegar mais o jeito. Considero mais
importante entender o exercício 1.4 e fazer (ou tentar) os outros.

Mas voltando a tua necessidade. Não deixa de ser um exercício
interessante. Certamente existem diversas formas de resolvê-lo e
implementar um doctest-like em Scheme. Vejo dois problemas:

1. Diversas implementações de Scheme possuem diversas características
e podem ser incompatíveis entre sí. Simplesmente gravar o que foi
feito em um pode não funcionar em outro.

2. Sei lá, mas deve ter. :-)

Solução? Que tal simplesmente copiar o exercício 1.1? Para facilitar a
leitura, poderíamos deixar uma linha em branco entre cada expressão.
Depois seria só colocar o resultado do que for cabível, após as
expressões e verificar o que está certo e errado (ou poderias colocar
'XXX' para que os alunos coloquem a resposta correta. Então vamos ao
programa (não te preocupa que não chega nas 500 linhas).

Primeiro é definir a string que conterá o que será testado (é só
copiar e colar). Poderia ser um arquivo texto, não precisaria das
linhas em branco e o código poderia ser mais bagunçado). Coloquei os
resultados, mas poderias colocar 'XXX' para que fossem digitados
posteriormente.

;; ----------------------------------------------------inicio
;;
(define s ";;;; inicio

10
10

(define (soma a b)
(+ a b))

(soma 3 3) ;; 3+3=6

56

(define (fatorial n)
(if (= n 1)
1
(* n (fatorial (- n 1)))))
(fatorial 5)
120


(fatorial 36)
371993326789901217467999448150835200000000


"
)
;; ----------------------------------------------------fim


Agora a parte legal.

;; ----------------------------------------------------
;; doctest Gambit V4.4.4
;;
(define (doctest f)
(let ((sexp 0) (res 0) (test 0))
(let loop ()
(set! sexp (read f))
(if (eof-object? sexp)
(exit))
(display sexp)
(set! res (eval sexp))
(if (equal? res (void))
(newline)
(begin
(display " => ")
(display res)
(newline)
(set! test (read f))
(if (eof-object? sexp)
(error "Fim inexperado do arquivo."))
(display test)
(if (equal? res (eval test))
(display " <= OK")
;;(error "Valor informado não confere."))
(display " <= ERRO"))
(newline)
))
(loop))))
;; ----------------------------------------------------

Só??? Só.

Bem, na realidade não. Podes trocar as linhas 'error' e 'display' do
último 'if' se a idéia for encerrar o programa no primeiro erro ou
apenas indicar que houve erro e continuar (um erro pode deixar o teste
inconsistente (lembre-se que nada indica onde está a avaliação e a
resposta). É possível melhorar o programa. e outras coisinhas.

Para executar, é só informar que ele deve ler da string digitada no
programa e chamar doctest indicando onde estão os testes.

;; ----------------------------------------------------
;;
(define p (open-input-string s))
(doctest p)
;; ----------------------------------------------------

Obs.: Estou usando o Emacs + Gambit e funcionou redondinho aqui. No
Chicken acusou erro no último fatorial. Não tenho outros instalados
portanto, nem idéia se vai funcionar. Também não pretendo testar no
GIMP nem no Emacs.

Desculpe as piadinhas.

--
Guaracy Monteiro

http://fotomix.wordpress.com/

Luciano Ramalho

unread,
Jul 11, 2009, 1:16:12 PM7/11/09
to ei...@googlegroups.com
2009/7/11 Guaracy Monteiro <guara...@gmail.com>:

> Não achei muito interessante mas achei legal pois serve como prova
> automática. O professor só precisa executar o programa para dar a nota
> para o aluno. Serviria para o exercício 1.1, mas mesmo assim eu
> prefiro digitar o código para pegar mais o jeito. Considero mais
> importante entender o exercício 1.4 e fazer (ou tentar) os outros.

O sistema de doctest não foi criado para a finalidade de fazer
correção automática de exercícios. Nos cursos de Python onde tenho
usado doctests, eles servem para apresentar de uma maneira objetiva o
problema a ser resolvido pelo aluno, ou seja, uma espécie de TDD
aplicado ao contexto de um curso.

Eu também acho que o exercício 1.4 é um dos mais interessantes da
primeira série, especialmente para quem nunca viu uma linguagem onde
funções são objetos de primeira classe, e os 1.5 e 1.6 são os mais
legais até agora para entender o processo de avaliação (mas ainda não
fiz os demais).

> Mas voltando a tua necessidade. Não deixa de ser um exercício
> interessante. Certamente existem diversas formas de resolvê-lo e
> implementar um doctest-like em Scheme. Vejo dois problemas:
>
> 1. Diversas implementações de Scheme possuem diversas características
> e podem ser incompatíveis entre sí. Simplesmente gravar o que foi
> feito em um pode não funcionar em outro.

Acho que na cultura Lisp inovar é mais importante do que manter
compatibilidade entre implementações, né? Como prova de conceito, uma
implementação que funcionasse em apenas um Scheme específico já seria
bom demais.

>
> 2. Sei lá, mas deve ter. :-)
>
> Solução? Que tal simplesmente copiar o exercício 1.1? Para facilitar a
> leitura, poderíamos deixar uma linha em branco entre cada expressão.
> Depois seria só colocar o resultado do que for cabível, após as
> expressões e verificar o que está certo e errado (ou poderias colocar
> 'XXX' para que os alunos coloquem a resposta correta. Então vamos ao
> programa (não te preocupa que não chega nas 500 linhas).

Só para deixar claro: o XXX não tem nada a ver com a sintaxe dos
doctests. É apena uma convenção que eu uso para orientar os alunos. Às
vezes, na linha de entrada ou na linha de saída eu coloco uma
expressão onde apenas parte foi substituida por XXX. E a maioria dos
doctests que eu uso com alunos nem tem esses XXX, mas seguem mais o
formato do segundo arquivo que eu indiquei, ou seja, funcionam como
uma forma de descrever o comportamento de uma função com diferentes
argumentos.

> Primeiro é definir a string que conterá o que será testado (é só
> copiar e colar). Poderia ser um arquivo texto, não precisaria das
> linhas em branco e o código poderia ser mais bagunçado). Coloquei os
> resultados, mas poderias colocar 'XXX' para que fossem digitados
> posteriormente.

[... codigo...]
> Agora a parte legal.
[... mais codigo...]


> Só??? Só.
>
> Bem, na realidade não. Podes trocar as linhas 'error' e 'display' do
> último 'if' se a idéia for encerrar o programa no primeiro erro ou
> apenas indicar que houve erro e continuar (um erro pode deixar o teste
> inconsistente (lembre-se que nada indica onde está a avaliação e a
> resposta). É possível melhorar o programa. e outras coisinhas.

Legal, Guaracy. Eu acho importante que exista uma distinção visual
entre as linhas a serem avaliadas e as linhas que representam
resultados das avaliações.

>
> Para executar, é só informar que ele deve ler da string digitada no
> programa e chamar doctest indicando onde estão os testes.
>
> ;; ----------------------------------------------------
> ;;
> (define p (open-input-string s))
> (doctest p)
> ;; ----------------------------------------------------
>
> Obs.: Estou usando o Emacs + Gambit e funcionou redondinho aqui. No
> Chicken acusou erro no último fatorial. Não tenho outros instalados
> portanto, nem idéia se vai funcionar. Também não pretendo testar no
> GIMP nem no Emacs.

Testei no DrScheme com o dialeto "Pretty Big" e funcionou
perfeitamente, inclusive o fatorial. Qual foi o erro no fatorial no
Chicken?

Tomei a liberdade de colocar o seu código no repositório [1].

http://code.google.com/p/eipc/source/browse/util/doctest-gm.scm

Se algum colega quiser comentar, o Googlecode tem um sistema de
code-review muito bom, que permite comentários linha a linha até.
Basta estar autenticado e dar um duplo-clique em uma linha de código
em [1].

Valeu, Guaracy!

[ ]s
Luciano

guaracy

unread,
Jul 12, 2009, 1:25:48 AM7/12/09
to Estrutura e Interpretação de Programas de Computador
> O sistema de doctest não foi criado para a finalidade de fazer
> correção automática de exercícios. Nos cursos de Python onde tenho
> usado doctests, eles servem para apresentar de uma maneira objetiva o
> problema a ser resolvido pelo aluno, ou seja, uma espécie de TDD
> aplicado ao contexto de um curso.

Ok, fui dar uma olhadinha em: http://docs.python.org/library/doctest.html
para uma idéia melhor do doctest. Bem prático. Me lembrei de D que,
apesar de mais tradicional, também é bem prático fazer/efetuar os
testes.

> Acho que na cultura Lisp inovar é mais importante do que manter
> compatibilidade entre implementações, né? Como prova de conceito, uma
> implementação que funcionasse em apenas um Scheme específico já seria
> bom demais.

Eu acho que em tudo que existe mais de um existe incompatibilidades.
Tem uma ANSI SQL mas cada SGDB possui suas peculiaridades. A gente
tem que conviver.

> Só para deixar claro: o XXX não tem nada a ver com a sintaxe dos
> doctests. É apena uma convenção que eu uso para orientar os alunos. Às
> vezes, na linha de entrada ou na linha de saída eu coloco uma
> expressão onde apenas parte foi substituida por XXX. E a maioria dos
> doctests que eu uso com alunos nem tem esses XXX, mas seguem mais o
> formato do segundo arquivo que eu indiquei, ou seja, funcionam como
> uma forma de descrever o comportamento de uma função com diferentes
> argumentos.

Agora eu entendi melhor.

> Legal, Guaracy. Eu acho importante que exista uma distinção visual
> entre as linhas a serem avaliadas e as linhas que representam
> resultados das avaliações.

Se me lembro, lisp permitia a inclusão de uma string dentro da função
para efeito documentativo. Não sei quanto ao Scheme. O meu
programinha pode e deve ser melhorado (não testei o suficiente para
saber o que pode causar erro).

Colocar um '>' no início da linha que deveria ser executada não deve
ser problema. O read vai retornar > . Falta o resto.


> Testei no DrScheme com o dialeto "Pretty Big" e funcionou
> perfeitamente, inclusive o fatorial. Qual foi o erro no fatorial no
> Chicken?

O Chicken de o seguinte erro:

(fatorial 36) => 3.71993326789901e+41
3.71993326789901e+41 <= ERRO

Teria que ser alguém que conheça mais a implementação para
ver onde está o problema. O meu conhecimento de Scheme
se resume a alguns scripts para o GIMP (na base do cola, copia,
altera)

> Tomei a liberdade de colocar o seu código no repositório [1].
>
> http://code.google.com/p/eipc/source/browse/util/doctest-gm.scm

Ok.

> Se algum colega quiser comentar, o Googlecode tem um sistema de
> code-review muito bom, que permite comentários linha a linha até.
> Basta estar autenticado e dar um duplo-clique em uma linha de código
> em [1].
>
> Valeu, Guaracy!

Por nada.

Guaracy

Luciano Ramalho

unread,
Jul 12, 2009, 5:57:22 AM7/12/09
to ei...@googlegroups.com
2009/7/12 guaracy <guara...@gmail.com>:

> Colocar um '>' no início da linha que deveria ser executada não deve
> ser problema. O read vai retornar > . Falta o resto.

Uma outra coisa é distinguir trechos que não devem ser avaliados, para
permitir que exista texto em português (ou outra lingua natural)
explicando os exemplos, como se faz no doctest. Aí o doctest vira
quase uma forma de "literate programming".

>> Testei no DrScheme com o dialeto "Pretty Big" e funcionou
>> perfeitamente, inclusive o fatorial. Qual foi o erro no fatorial no
>> Chicken?
>
> O Chicken de o seguinte erro:
>
> (fatorial 36) => 3.71993326789901e+41
> 3.71993326789901e+41 <= ERRO

Certo, parece que o Chicken promove inteiros para floats em caso de
overflow. Goulart, o Chicken tem uma representação de inteiros que não
se limita ao tamanho dos registradores da máquina?

Apesar de dos valores inexatos, num teste aqui no PLT-Scheme vi que o
operador equal funciona para comparar esses floats. Mas não dá para
confiar em equal para comparar floats, né?

Por isso o doctest tem um jeito de você representar uma saída com uma
precisão limitada (usando ...) e outros frameworks de teste
automatizado como PyUnit (e XUnit de uma forma geral) tem testes
especiais para comparar floats usando um epsilon como tolerância.

Na disciplina que o Rodrigo Pimentel e eu cursamos na USP no primeiro
semestre a gente usava o PLT-Scheme com a linguagem PLAI Scheme [1],
que inclui um esqueminha de testes bem razoável, e tem uma função
test-inexact-epsilon que define uma tolerância usada nas comparações
entre floats.

[1] http://planet.plt-scheme.org/package-source/plai/plai.plt/1/1/planet-docs/plai/index.html

>> Tomei a liberdade de colocar o seu código no repositório [1].
>>
>> http://code.google.com/p/eipc/source/browse/util/doctest-gm.scm
>
> Ok.

Olhando de novo para o código me chama atenção o estilo bastante
imperativo, em particular pelo uso de set! e o controle do loop com if
e exit. É a natureza do problema, dirigido a I/O, que levou a uma
solução assim, ou é o seu estilo pessoal de programar Scheme, Guaracy?
Pergunto como um novato completo em Scheme, que nunca escreveu um
programa que lê um arquivo.

[ ]s
Luciano

Luciano Ramalho

unread,
Jul 12, 2009, 7:14:24 AM7/12/09
to Estrutura e Interpretação de Programas de Computador
On 12 jul, 06:57, Luciano Ramalho <rama...@gmail.com> wrote:
> Certo, parece que o Chicken promove inteiros para floats em caso de
> overflow. Goulart, o Chicken tem uma representação de inteiros que não
> se limita ao tamanho dos registradores da máquina?

Achei a resposta [1]:

"""
[6.2.5] There is no built-in support for rationals, complex numbers or
extended-precision integers (bignums). The routines complex?, real?
and rational? are identical to the standard procedure number?. The
procedures numerator, denominator, rationalize, make-rectangular and
make-polar are not implemented. Fixnums are limited to ±230 (or ±262
on 64-bit hardware). Support for extended numbers is available as a
separate package, provided the GNU multiprecision library is
installed.
"""

[1] http://chicken.wiki.br/man/4/Deviations%20from%20the%20standard

E este outro trecho talvez também ajude a explicar porque a comparação
com floats aparentemente idênticos não funcionou:

"""
[6.2.4] The runtime system uses the numerical string-conversion
routines of the underlying C library and so does only understand
standard (C-library) syntax for floating-point constants.
"""

[ ]s
Luciano

Luciano Ramalho

unread,
Jul 12, 2009, 7:53:10 AM7/12/09
to Estrutura e Interpretação de Programas de Computador
On 12 jul, 08:14, Luciano Ramalho <rama...@gmail.com> wrote:
> On 12 jul, 06:57, Luciano Ramalho <rama...@gmail.com> wrote:
>
> > Certo, parece que o Chicken promove inteiros para floats em caso de
> > overflow. Goulart, o Chicken tem uma representação de inteiros que não
> > se limita ao tamanho dos registradores da máquina?
>
> Achei a resposta [1]:
>
> """
> [6.2.5] There is no built-in support for rationals, complex numbers or
> extended-precision integers (bignums). The routines complex?, real?
> and rational? are identical to the standard procedure number?. The
> procedures numerator, denominator, rationalize, make-rectangular and
> make-polar are not implemented. Fixnums are limited to ±230 (or ±262
> on 64-bit hardware). Support for extended numbers is available as a
> separate package, provided the GNU multiprecision library is
> installed.
> """
>
> [1]http://chicken.wiki.br/man/4/Deviations%20from%20the%20standard

Complentando eu mesmo...

Embora o Chicken não implemente nativamente os tipos numéricos do
comuns em Scheme (conhecida como "numeric tower"), existe uma extensão
chamada numbers que pode ser instalada. Veja o FAQ:

http://chicken.wiki.br/man/4/faq#why-doesn-t-chicken-support-the-full-numeric-tower-by-default

[ ]s
Luciano

Mario Domenech Goulart

unread,
Jul 12, 2009, 10:17:02 AM7/12/09
to ei...@googlegroups.com
Alô pessoal.

On Sun, 12 Jul 2009 06:57:22 -0300 Luciano Ramalho <ram...@gmail.com> wrote:

> 2009/7/12 guaracy <guara...@gmail.com>:

>>> Testei no DrScheme com o dialeto "Pretty Big" e funcionou
>>> perfeitamente, inclusive o fatorial. Qual foi o erro no fatorial no
>>> Chicken?
>>
>> O Chicken de o seguinte erro:
>>
>> (fatorial 36) => 3.71993326789901e+41
>> 3.71993326789901e+41 <= ERRO
>
> Certo, parece que o Chicken promove inteiros para floats em caso de
> overflow. Goulart, o Chicken tem uma representação de inteiros que não
> se limita ao tamanho dos registradores da máquina?

Como já foi mencionado em outra mensagem, somente através da extensão
numbers (http://chicken.wiki.br/eggref/4/numbers), baseada na libgmp.

Um abraço.
Mario

Guaracy Monteiro

unread,
Jul 12, 2009, 11:10:17 PM7/12/09
to ei...@googlegroups.com
>> Colocar um '>' no início da linha que deveria ser executada não deve
>> ser problema. O read vai retornar > . Falta o resto.
>
> Uma outra coisa é distinguir trechos que não devem ser avaliados, para
> permitir que exista texto em português (ou outra lingua natural)
> explicando os exemplos, como se faz no doctest. Aí o doctest vira
> quase uma forma de "literate programming".

Seria só utilizar o ponto e vírgula que o resto da linha seria tratado
como comentário pelo read. Descobri que o Scheme também aceita uma
string dentro da função, só não sei como recuperá-lo.

(define (soma a b)
"doctest pode ficar aqui"
(+ a b))

> Certo, parece que o Chicken promove inteiros para floats em caso de
> overflow. Goulart, o Chicken tem uma representação de inteiros que não
> se limita ao tamanho dos registradores da máquina?
>
> Apesar de dos valores inexatos, num teste aqui no PLT-Scheme vi que o
> operador equal funciona para comparar esses floats. Mas não dá para
> confiar em equal para comparar floats, né?

Bem, foi o primeiro programa (fora os exercícios) de alguém que está
tendo um maior contato com a linguagem. Espero que até o final do livro
o programa seja usável. :-)

> Por isso o doctest tem um jeito de você representar uma saída com uma
> precisão limitada (usando ...) e outros frameworks de teste
> automatizado como PyUnit (e XUnit de uma forma geral) tem testes
> especiais para comparar floats usando um epsilon como tolerância.

Bem, vamos ver as próximas versões e, se no final, não sai alguma coisa
interessante e bem usável.

> Na disciplina que o Rodrigo Pimentel e eu cursamos na USP no primeiro
> semestre a gente usava o PLT-Scheme com a linguagem PLAI Scheme [1],
> que inclui um esqueminha de testes bem razoável, e tem uma função
> test-inexact-epsilon que define uma tolerância usada nas comparações
> entre floats.

Depois eu olho com mais calma.

> [1] http://planet.plt-scheme.org/package-source/plai/plai.plt/1/1/planet-docs/plai/index.html

> Olhando de novo para o código me chama atenção o estilo bastante
> imperativo, em particular pelo uso de set! e o controle do loop com if
> e exit. É a natureza do problema, dirigido a I/O, que levou a uma
> solução assim, ou é o seu estilo pessoal de programar Scheme, Guaracy?
> Pergunto como um novato completo em Scheme, que nunca escreveu um
> programa que lê um arquivo.

Acho que é porque eu não conheço muito bem a linguagem e não utilizo
linguagens funcionais. Foi o fluxo que me veio a cabeça na hora. Não sei
como alguém que conhece a linguagem abordaria o problema. Até tenho
uma leve noção de Lisp, Scheme e outras mas nunca fiz nada maior do
que algumas linhas nem me aprofundei na leitura de um programa. Este
foi o motivo para eu participar do grupo, isto é, ler o livro que sempre foi
citado como uma ótima fonte de referência e conhecer a linguagem
melhor.

Mario Domenech Goulart

unread,
Jul 13, 2009, 7:18:33 AM7/13/09
to ei...@googlegroups.com
Alô Guaracy

On Mon, 13 Jul 2009 00:10:17 -0300 Guaracy Monteiro <guara...@gmail.com> wrote:

>>> Colocar um '>' no início da linha que deveria ser executada não deve
>>> ser problema. O read vai retornar > . Falta o resto.
>>
>> Uma outra coisa é distinguir trechos que não devem ser avaliados, para
>> permitir que exista texto em português (ou outra lingua natural)
>> explicando os exemplos, como se faz no doctest. Aí o doctest vira
>> quase uma forma de "literate programming".
>
> Seria só utilizar o ponto e vírgula que o resto da linha seria tratado
> como comentário pelo read. Descobri que o Scheme também aceita uma
> string dentro da função, só não sei como recuperá-lo.
>
> (define (soma a b)
> "doctest pode ficar aqui"
> (+ a b))

O exemplo de implementação que enviei [1] faz exatamente isso (veja o
procedimento `pick-teststrings').

[1] http://groups.google.com/group/eipc/msg/a2b6300255ce6e8e?hl=pt-BR

Ler a string do corpo do procedimento é pegar o car do cdr do cdr
(caddr) da lista

(define (proc args) "doctest" corpo)
| | |
car <-| | |
| |
cadr <---------| |
|
caddr <-------------------|

se:

* o car dela for igual a `define'

* o cadr for um par, o que permite definições do tipo:

(define (proc . rest-args) "docstring" corpo)

* o caddr for uma string

Um abraço.
Mario

guaracy

unread,
Jul 15, 2009, 2:01:21 AM7/15/09
to Estrutura e Interpretação de Programas de Computador
> O exemplo de implementação que enviei [1] faz exatamente isso (veja o
> procedimento `pick-teststrings').
>
> [1]http://groups.google.com/group/eipc/msg/a2b6300255ce6e8e?hl=pt-BR

No final de semana vou olhar com mais carinho os exemplos que
enviaste.

Guaracy
Reply all
Reply to author
Forward
0 new messages