Elmord's Magic Valley

Computers, languages, and computer languages. Às vezes em Português, sometimes in English.

Posts com a tag: em-portugues

Tour de Scheme, parte 1: Tipos básicos, expressões de vários sabores, e um jogo de adivinhações

2016-04-06 03:06 -0300. Tags: comp, prog, lisp, scheme, tour-de-scheme, em-portugues

[Este post é parte de uma série sobre Scheme.]

No último episódio, vimos como compilar e rodar programas em Scheme usando o Chicken. Neste post, veremos algumas construções de Scheme e as diferenças em relação às construções equivalentes nas linguagens HtDP. Em seguida, veremos como escrever um programinha clássico de "adivinhe o número" em Scheme.

[Screenshot do programa de adivinhar o número]
Cores por cortesia do shell-mode do Emacs

Você pode acompanhar os exemplos usando o DrRacket ao invés do Chicken, se preferir, mas para isso você deverá abrir a janela "Choose Language" (Ctrl+L), escolher "The Racket Language", clicar no botão "Show Details", e mudar o Output Style para "write" ao invés de "print".

Tipos básicos

Você deve lembrar dos tipos de dados básicos das linguagens HtDP (se não lembrar tudo bem, pois nós vamos revê-los agora):

Se você entrar algum desses valores em um prompt de avaliação, o resultado será o próprio valor:

#;1> -6.25
-6.25
#;2> "Hello, world!\n"
"Hello, world!\n"
#;3> #\h
#\h
#;4> #t
#t
#;5> 'foo
foo

Hmm, aqui temos uma curiosidade: diferente do que acontecia nas linguagens HtDP, 'foo produz foo sem o apóstrofe. Vamos fazer uma nota mental desse detalhe e seguir adiante.

Expressões compostas

Nem só de expressões constantes vive o ser humano. Um programa em Scheme consiste majoritariamente de chamadas de função e formas especiais. Chamadas de função são escritas na forma (função argumento1 argumento2...), i.e., a função e os argumentos separados por espaços e envoltos em parênteses. Por exemplo:

#;1> (display "Hello, world!\n")
Hello, world!
#;2> (sqrt 16)
4.0
#;3> (expt 5 3)
125

Operadores aritméticos em Scheme são funções comuns, e são usados exatamente como as demais funções, i.e., se escreve (operador argumentos...):

#;1> (+ 2 3)
5
#;2> (* 2 3)
6

Vale notar que essas operações aceitam um número arbitrário de argumentos:

#;1> (+ 1 2 3 4)
10

Naturalmente, você pode usar o resultado de uma chamada de função como argumento para outra chamada:

#;2> (expt (+ 1 2 3 4) 2)
100
#;3> (* 10 (sqrt 16))
40.0

Operadores de comparação também são funções:

#;1> (< 2 3)
#t
#;2> (> 2 3)
#f

Formas especiais são escritas da mesma maneira, mas usando um operador especial ao invés de uma função. Exemplos de operadores especiais são:

A diferença entre uma chamada de função e uma forma especial é que em uma chamada de função, todos os argumentos são avaliados como expressões, antes mesmo de a função começar a executar, enquanto em uma forma especial, os argumentos não são necessariamente todos avaliados ou considerados como expressões. (No caso do if e cond, certos argumentos só são avaliados dependendo dos valores das condições. No caso do define, o nome da variável ou função simplesmente não é avaliado: (define valor 5) não tenta avaliar valor, mas sim toma-o literalmente como o nome da variável.)

Exemplo: Adivinhe o número

Vamos exercitar um pouco do que vimos até agora escrevendo um programinha clássico que sorteia um número de 1 a 100 e fica pedindo ao usuário que tente adivinhar o número até o usuário acertar ou morrer de tédio. Quando o usuário erra, informamos se o seu chute foi muito alto ou muito baixo. Para isso, vamos precisar de umas funçõezinhas extra ainda não vistas:

Ok. Vamos começar escrevendo uma função para perguntar um número ao usuário e ler a resposta:

(define (pergunta-número)
  (display "Digite um número: ")
  (read-line))

Salve essa função em um arquivo adivinha.scm e carregue-o no interpretador:

#;1> ,l adivinha.scm
; loading adivinha.scm ...

Agora, podemos testar a função:

#;1> (pergunta-número)
Digite um número: 13
"13"

Que sucesso, hein? Só que queremos que a função retorne um número, não uma string (pois precisamos comparar o número digitado com a resposta certa mais tarde). Vamos modificar a pergunta-número para converter o resultado para número antes de retornar:

(define (pergunta-número)
  (display "Digite um número: ")
  (string->number (read-line)))

Note que:

Isso é diferente das linguagens HtDP, que só permitem uma expressão no corpo da função.

Ok, enough talk. Vamos recarregar o arquivo no interpretador e testar:

#;2> ,l adivinha.scm
; loading adivinha.scm ...
#;2> (pergunta-número)
Digite um número: 13
13

Buenacho barbaridade. Agora vamos escrever uma função que recebe um número (a resposta certa), lê o chute do usuário, guardando-o em uma variável local, compara-o com a resposta certa, e imprime uma mensagem apropriada:

(define (avalia-tentativa gabarito)
  (define tentativa (pergunta-número))
  (cond [(= tentativa gabarito) (display "Você acertou! Parabéns!\n")]
        [(< tentativa gabarito) (display "Muito baixo!\n")]
        [(> tentativa gabarito) (display "Muito alto!\n")]))

Vamos ver se isso funciona? Vamos chamar a função com 42 como a resposta certa, e responder o prompt com 13:

#;1> ,l adivinha.scm
; loading adivinha.scm ...
#;1> (avalia-tentativa 42)
Digite um número: 13
Muito baixo!
#;2>

Well, funcionou, pelo menos para esse caso. O causo é, todavia, que a gente quer que a função fique repetindo enquanto o usuário não acertar. Há várias maneiras de fazer esse loop, mas a maneira mais simples, usando só o que a gente viu até agora, é fazer a função simplesmente chamar a si mesma se a resposta não é a esperada:

(define (avalia-tentativa gabarito)
  (define tentativa (pergunta-número))
  (cond [(= tentativa gabarito) (display "Você acertou! Parabéns!\n")]
        [(< tentativa gabarito) (display "Muito baixo!\n")
                                (avalia-tentativa gabarito)]
        [(> tentativa gabarito) (display "Muito alto!\n")
                                (avalia-tentativa gabarito)]))

Ok, dava para simplificar várias coisas nesse código (e.g., unificar os dois últimos casos para escrever a re-chamada só uma vez), mas por enquanto está bom assim. (Se você está vindo de uma linguagem não-funcional, você pode estar pensando que é super-ineficiente usar uma chamada de função ao invés de um loop para repetir o corpo. Em Scheme, todavia, essa chamada é tão eficiente quanto um loop, devido a uma feature chamada tail call optimization, que nós vamos deixar para discutir em outra oportunidade.)

Vamos lá testar as esferas do dragão:

#;7> ,l adivinha.scm
; loading adivinha.scm ...
#;7> (avalia-tentativa 42)
Digite um número: 13
Muito baixo!
Digite um número: 81
Muito alto!
Digite um número: 42
Você acertou! Parabéns!
#;8> 

Agora sim. Só falta a parte do programa que gera o número aleatório e chama avalia-tentativa, i.e., falta o equivalente da "main" do nosso programa. Nós podemos escrever o código dentro de uma função main para fins de organização (e de poder testar a função a partir do prompt do interpretador), mas, como mencionado no último post, não existe em Scheme uma função especial "main" por onde a execução começa. O programa simplesmente executa todas as expressões no corpo do programa em seqüência. Então, podemos simplesmente atirar geração do número aleatório e chamada a avalia-tentativa no corpo do programa (lembrado de pôr a chamada depois da definição de avalia-tentativa; caso contrário, a função ainda não vai estar definida quando ocorrer a chamada); ou então podemos colocar isso numa função (again, apenas para fins de organização), mas aí temos que chamar a função no corpo do programa. Algo como:

;; Define a função...
(define (main)
  (avalia-tentativa (+ 1 (random 100))))

;; ...e a chama no corpo do programa, para que ela seja executada quando o programa iniciar.
(main)

Note que chamamos avalia-tentativa com (+ 1 (random 100)), pois (random 100) retorna um número entre 0 e 99, mas queremos um número entre 1 e 100, e para isso somamos 1 ao resultado.

Podemos testar no interpretador, mas agora que está tudo feito, podemos ao invés disso compilar o programa e testar o executável diretamente:

$ csc adivinha.scm
$ ./adivinha 

Error: unbound variable: random

	Call history:

	adivinha.scm:19: main	  
	adivinha.scm:15: random	  	<--

Ei! Que sacanagem é essa? Well, acontece que random é definida em um pacote que é carregado automaticamente pelo interpretador, mas não no código compilado. Isso é um acontecimento relativamente comum no Chicken. Para resolver isso, primeiro precisamos saber qual é o pacote que contém a função random. Para isso, podemos usar o chicken-doc, que vimos como instalar no post anterior:

$ chicken-doc random
Found 2 matches:
(random-bsd random)  (random N)
(extras random)      (random N)

Well, temos duas opções. random-bsd é um egg (que não vem por padrão com o Chicken e você pode instalar com o chicken-install). extras é um pacote/unit/como-preferir que acompanha o Chicken e não requer a instalação de nada especial. É esse que vamos usar. Tudo o que precisamos é adicionar a linha:

(use extras)

no começo do nosso código. Agora, podemos recompilar e testar:

$ csc adivinha.scm
$ ./adivinha 
Digite um número: 32
Muito alto!
Digite um número: 10
Muito baixo!
Digite um número: 20
Muito alto!
Digite um número: 15
Você acertou! Parabéns!
$

Uff! Funcionou. Nosso programa inteiro ficou assim:

(use extras)

(define (pergunta-número)
  (display "Digite um número: ")
  (string->number (read-line)))

(define (avalia-tentativa gabarito)
  (define tentativa (pergunta-número))
  (cond [(= tentativa gabarito) (display "Você acertou! Parabéns!\n")]
        [(< tentativa gabarito) (display "Muito baixo!\n")
                                (avalia-tentativa gabarito)]
        [(> tentativa gabarito) (display "Muito alto!\n")
                                (avalia-tentativa gabarito)]))

(define (main)
  (avalia-tentativa (+ 1 (random 100))))

(main)

Exercício: Experimente modificar o programa para, quando o usuário acertar, mostrar quantas tentativas foram usadas para adivinhar o número. Como conservar essa contagem durante a execução do loop (que na verdade é uma função que chama a si própria)?

Dica: Para imprimir uma mensagem como "Você acertou em N tentativas", você pode simplesmente usar vários displays em seqüência, i.e.:

(display "Você acertou em ")
(display N)
(display " tentativas\n")

Ou, em Chicken e Racket, você pode usar (printf "Você acertou em ~a tentativas" N). printf não é uma função padrão do Scheme R5RS, mas sim uma extensão do Chicken, Racket e possivelemente outras implementações.

Closing remarks

No nosso exemplo, definimos uma função separada para perguntar o número ao usuário, ao invés de fazer a pergunta diretamente na função avalia-tentativa. Eu fiz isso por dois motivos.

O primeiro é que é considerado bom estilo em Scheme definir funções pequenas com um propósito bem definido. Não só porque o código tende a ficar mais legível, mas também porque isso facilita testar separadamente cada parte do programa a partir do prompt de avaliação. Como você pôde observar neste post, o estilo normal de desenvolvimento em Scheme é ir escrevendo o programa aos poucos, recarregando as modificações no prompt de avaliação, e testando à medida em que se escreve. (Nada lhe impede de escrever o programa inteiro e testar depois, mas, mesmo nesses casos, poder chamar as diversas partes do programa individualmente a partir do prompt é útil para debugar o programa e descobrir a origem de um erro.)

Por falar em bom estilo, em Scheme é considerado bom estilo dar nomes descritivos às funções (principalmente) e às variáveis. Acostume-se a dar nomes-descritivos-separados-por-traços às funções (como fizemos neste post), e a usar o recurso de auto-completar do seu editor favorito para não se cansar digitando (Ctrl+N no Vim, Alt+/ no Emacs, configurável em ambos).

O segundo motivo é que, em Scheme R5RS, defines aninhados só podem aparecer no começo do corpo da função (e no começo de algumas outras formas especiais ainda não vistas), i.e., não podemos escrever:

(define (avalia-tentativa gabarito)
  (display "Digite um número: ")
  (define tentativa (string->number (read-line)))
  (cond ...))

pois o define não é a primeira coisa no corpo da função. Diversas implementações de Scheme permitem defines fora do começo da função, incluindo o próprio Chicken, mas os detalhes e o comportamento variam de Scheme para Scheme e podem trazer umas surpresas desagradáveis, e portanto eu prefiro evitá-los. Para definir variáveis fora do começo da função, pode-se usar a forma let, cuja sintaxe é:

(let ([variável1 valor1]
      [variável2 valor2]
      ...)
  corpo no qual as variáveis são visíveis)

Assim, poderíamos escrever:

(define (avalia-tentativa gabarito)
  (display "Digite um número: ")
  (let ([tentativa (string->number (read-line))])
    (cond ...)))

A forma com a função pergunta-número separada é mais legível, anyway.

Aproveito a oportunidade para mencionar que parênteses e colchetes são totalmente intercambiáveis em Chicken, Racket, Guile e provavelmente outras implementações. Você pode usar apenas parênteses, se preferir (em Scheme R5RS puro apenas os parênteses são definidos), mas costuma-se usar colchetes em formas como let e cond para facilitar a leitura.

(close (current-post))

Por hoje ficamos por aqui. Eu pretendia falar sobre listas neste post originalmente, mas vai ficar para o próximo da série. Como sempre, sugestões, comentários, dúvidas, reclamações, etc., são sempre bem-vindos.

11 comentários / comments

Tour de Scheme, parte 0

2016-03-31 23:57 -0300. Tags: comp, prog, lisp, scheme, tour-de-scheme, em-portugues

[Ok, tenho que publicar isso aqui logo antes que seja primeiro de abril e achem que o texto é zoeira.]

Aqui na INF nós temos uma cadeira chamada Fundamentos de Algoritmos. Nessa cadeira, a galera programa usando um subset didático de Scheme, as linguagens do How to Design Programs, no ambiente DrRacket (anteriormente conhecido como DrScheme). As linguagens HtDP são bastante limitadas, então normalmente os alunos (incluindo eu mesmo, quando fiz a cadeira) saem com a impressão de que Scheme é uma linguagem limitada, sem utilidade prática, e cheia de parênteses supérfluos.

O meu objetivo aqui é fazer um tour pela linguagem Scheme tal como ela é usada "no mundo real". Este post é o primeiro de uma (provável) série de posts, nos quais eu pretendo cobrir, sem nenhum compromisso com uma ordem muito fixa (muito menos com um cronograma) os seguintes tópicos:

Comentários são bem-vindos, e podem guiar o rumo da série se alguém tiver interesse em tópicos específicos.

Então, lá vamos nós.

[Screenshot de Hello World em Scheme]
Se eu não mostrar logo um "Hello World!" em Scheme, ninguém vai continuar lendo

Lisp, Scheme, e um pouco de história

Lisp é uma família de linguagens que brotaram em última instância do Lisp original (que naquela época se escrevia LISP), criado por John McCarthy em 1958. As linguagens mais importantes dessa família atualmente são Scheme, Common Lisp e Clojure.

O Scheme surgiu por volta de 1975, por obra de Guy Steele (também conhecido por seu envolvimento na padronização do Common Lisp e do Java) e Gerald Sussman (também conhecido pelo livro Structure and Interpretation of Computer Programs). Desde lá, surgiram diversas versões ("revisões") da linguagem. A versão mais amplamente implementada atualmente é o R5RS (Revised5 Report on Scheme). Depois disso surgiram o R6RS (que teve uma recepção um tanto controversa na comunidade Scheme, por uma série de razões), e o R7RS (uma revisão mais próxima do R5RS, mas que ainda não é amplamente suportada).

Scheme é uma linguagem minimalista: a definição do R5RS tem 50 páginas. Coisas como interação com o sistema operacional, manipulação do sistema de arquivos, threads, conexões de rede, etc. não são especificadas pelo report. Porém, essas funcionalidades são providas como extensões pelas diversas implementações de Scheme. Além de bibliotecas, as implementações freqüentemente também provêem extensões da linguagem Scheme em si. (Na verdade, Scheme, assim como os demais Lisps, possui uma sintaxe extensível, e muitas coisas que seriam consideradas extensões em outras linguagens são providas por bibliotecas em Scheme.)

Uma conseqüência disso é que, como essas features não são padronizadas, cada implementação de Scheme é livre para implementá-las como quiser. É mais ou menos como se cada implementação fosse um dialeto de Scheme diferente. Isso significa que programas Scheme não costumam ser diretamente portáveis entre implementações (pois qualquer programa não-trivial acaba usando features não definidas no report), mas isso não chega a ser um grande problema, pois a maioria das implementações mais importantes rodam em múltiplas plataformas (i.e., as implementações são elas próprias portáveis). Além disso, algumas dessas features fora do escopo do report são definidas como SRFIs (Scheme Request For Implementation), que são basicamente padrões definidos pela comunidade Scheme e suportados por diversas implementações (nem todas as SRFIs são suportados por todas as implementações, mas todas as implementações que suportam uma dada SRFI oferecem a mesma interface).

Implementações

Existem dúzias (literalmente) de implementações de Scheme, mas apenas algumas são ativamente mantidas. Há quem tenha mais recomendações de implementações, mas eu posso sugerir as seguintes três:

Para os exemplos neste post, usarei o Chicken. O básico vai funcionar em qualquer outro Scheme, mas os exemplos que usam bibliotecas são específicos do Chicken. Além disso, o post assume que estaremos rodando em GNU/Linux.

Editores de texto

Para programar em Scheme e demais Lisp, é meio que uma necessidade usar um editor de texto com suporte decente a indentação automática e highlighting de parênteses. Eu conheço três editores (e meio) que satisfazem esses requisitos: o Emacs, o Vim, o DrRacket e (meio que) o JED (com um addon de terceiros, e meio meia-boca pelo que eu testei). Tanto o Emacs quanto o Vim ativam automaticamente sintaxe e indentação adequadas quando você abre um arquivo com a extensão .scm (ou deveriam, pelo menos).

No Vim, você pode usar :set ai lisp caso a indentação não seja ajustada automaticamente, e :syntax on para habilitar syntax highlighting. Um comando particularmente útil é %, que salta para o parêntese que casa com o parêntese sob o cursor. Esse comando pode ser combinado com outros (e.g., c% para recortar uma expressão inteira entre parênteses).

No Emacs, tudo deveria funcionar normalmente de fábrica. Comandos particularmente úteis são Ctrl+Alt+setas para saltar por expressões (e.g., se você está sobre o abre-parêntese, Ctrl+Alt+→ salta para o fecha-parêntese), e Ctrl+Alt+K para recortar a expressão diante do cursor (e.g., você pode parar sobre o abre-parêntese e dar Ctrl+Alt+K para recortar toda a expressão). O Emacs também possui alguns modos para integração com um interpretador Scheme externo (o que permite um desenvolvimento a la DrRacket).

Nada impede que você use o DrRacket para editar o seu código e use outro Scheme para executá-lo (o que seria meio bizarro, mas quem é o mundo para lhe julgar?).

Usando o Chicken

Vamos começar vendo como rodar um programa com o Chicken. Crie um arquivo chamado hello.scm com o conteúdo:

(display "Hello, world!\n")

O Chicken vem com dois comandos principais: csc (Chicken Scheme Compiler) e csi (Chicken Scheme Interpreter). Vamos começar usando o compilador. Para compilar o arquivo, abra um terminal, entre no diretório onde você salvou o hello.scm, e execute:

$ csc hello.scm

(O $ é o prompt, não parte do comando.) Isso deve criar um executável chamado hello, que (no GNU/Linux e demais Unixes) você pode chamar com ./hello:

$ ./hello
Hello, world!

Ao invés de compilar o programa, podemos executá-lo no interpretador. Se você chamar csi hello.scm, o Chicken vai carregar o interpretador, interpretar o programa, e exibir um prompt esperando por expressões Scheme a avaliar. Você também pode chamar o csi sem um nome de arquivo para simplesmente abrir o prompt de avaliação.

$ csi hello.scm

CHICKEN
(c) 2008-2014, The Chicken Team
(c) 2000-2007, Felix L. Winkelmann
Version 4.9.0.1 (stability/4.9.0) (rev 8b3189b)
linux-unix-gnu-x86-64 [ 64bit manyargs dload ptables ]
bootstrapped 2014-06-07

; loading /home/vitor/.csirc ...
; loading /var/lib//chicken/7/readline.import.so ...
; loading /var/lib//chicken/7/chicken.import.so ...
; loading /var/lib//chicken/7/foreign.import.so ...
; loading /var/lib//chicken/7/ports.import.so ...
; loading /var/lib//chicken/7/data-structures.import.so ...
; loading /var/lib//chicken/7/posix.import.so ...
; loading /var/lib//chicken/7/irregex.import.so ...
; loading /var/lib//chicken/7/readline.so ...
; loading hello.scm ...
Hello, world!
#;1> 

O interpretador é particularmente útil quando queremos chamar as funções de um programa individualmente e ver os resultados, ao invés de rodar o programa inteiro toda vez que queremos testar algo. Por exemplo, vamos adicionar ao nosso hello.scm uma função greet, que recebe o nome de alguém e imprime uma saudação adequada:

(define (greet name)
  (display (string-append "Hello, " name "!\n")))

;; Imprime "Hello, world!\n" ao iniciar o programa, como no programa original.
(greet "world")

Agora se rodarmos o interpretador, o programa imprimirá "Hello, world!" como anteriormente, mas podemos chamar a função greet diretamente do prompt:

$ csi hello.scm

CHICKEN
(c) 2008-2014, The Chicken Team
(c) 2000-2007, Felix L. Winkelmann
Version 4.9.0.1 (stability/4.9.0) (rev 8b3189b)
linux-unix-gnu-x86-64 [ 64bit manyargs dload ptables ]
bootstrapped 2014-06-07

; loading /home/vitor/.csirc ...
; loading /var/lib//chicken/7/readline.import.so ...
; loading /var/lib//chicken/7/chicken.import.so ...
; loading /var/lib//chicken/7/foreign.import.so ...
; loading /var/lib//chicken/7/ports.import.so ...
; loading /var/lib//chicken/7/data-structures.import.so ...
; loading /var/lib//chicken/7/posix.import.so ...
; loading /var/lib//chicken/7/irregex.import.so ...
; loading /var/lib//chicken/7/readline.so ...
; loading hello.scm ...
Hello, world!
#;1> (greet "Hildur")
Hello, Hildur!
#;2> 

Isso é extremamente útil para debugar e testar programas. De fato, o método de desenvolvimento normal em Scheme é ir testando as funções à medida em que vai escrevendo o programa, ao invés de esperar até ter um programa completo para rodar. Note que aqui abrimos de novo o interpretador para carregar a nova versão do hello.scm, mas isso não é necessário: você pode usar (load "hello.scm") a partir do prompt de avaliação para recarregar o arquivo (ou carregar um novo arquivo), ou a forma abreviada ,l hello.scm.

A desvantagem de usar o interpretador é que o código interpretado é mais lento que o compilado. Quando se deseja ter tanto a velocidade do código compilado quanto a conveniência do prompt interativo, o que se pode fazer é compilar o programa como uma biblioteca compartilhada (shared library), que pode ser carregada no interpretador. Para isso, utiliza-se a opção -shared com o csc:

$ csc -shared hello.scm

Isso cria um arquivo hello.so (SO = Shared Object, a extensão de bibliotecas no GNU/Linux). Agora você pode rodar csi hello.so, ou rodar ,l hello.so a partir do prompt interativo, para carregar a biblioteca.

Pacotes e documentação

O Chicken possui um sistema de pacotes, chamados eggs, que podem ser tanto bibliotecas quanto executáveis independentes. Vamos começar instalando um egg particularmente útil: o chicken-doc, que permite ler e pesquisar a documentação do Chicken.

O primeiro passo é instalar o egg em si. Para isso, usamos o comando chicken-install, que se encarrega de baixar, compilar e instalar o egg:

$ chicken-install -sudo chicken-doc

O segundo passo é baixar e instalar a documentação propriamente dita, seguindo as instruções na página do chicken-doc:

$ cd `csi -p '(chicken-home)'`
$ curl http://3e8.org/pub/chicken-doc/chicken-doc-repo.tgz | sudo tar zx

(Você pode substituir o curl por wget -O -, se não tiver o curl na máquina.)

Se tudo deu certo, você agora deve conseguir rodar o comando chicken-doc pela linha de comando. Você pode rodá-lo sem parâmetros para ver todas as opções e exemplos de uso. A sintaxe básica é chicken-doc nome-da-função-ou-egg-de-interesse. Se houver mais de uma entrada com o mesmo nome na documentação, o chicken-doc lista todas as possibilidades. Por exemplo, se você rodar chicken-doc display, você verá algo como:

Found 3 matches:
(scheme display)           (display obj)
(allegro display display)  display
(allegro display)          display egg

A lista entre parênteses à esquerda é o caminho do item. Você pode rodar chicken-doc scheme display para ver a documentação do primeiro item, por exemplo. (Tecle q para sair do leitor.)

Note que a documentação do chicken-doc é basicamente um snapshot da wiki do Chicken. A mesma informação pode ser encontrada online (mas você pode achar o chicken-doc mais conveniente para pesquisar na documentação (eu acho, pelo menos)).

#!eof

Por hoje ficamos por aqui. No próximo post, deveremos ver as principais diferenças entre as construções das linguagens HtDP e os equivalentes em R5RS / Chicken (que também serve como uma introdução ao Scheme, para os leitores que não conheçam ou não lembrem das linguagens HtDP), algumas features novas, e uma introdução a programação funcional.

4 comentários / comments

"Pirataria"

2015-11-18 19:36 -0200. Tags: freedom, copyright, em-portugues

Hoje a Polícia Federal prendeu os administradores do site Mega Filmes HD e levou mais cinco pessoas supostamente envolvidas com o site para serem ouvidas.

"Mega Filmes HD" virou trending topic no Twitter (e ainda está no topo dos trending topics do Brasil). A maioria dos tweets é gente inconformada com o fato de que o site está para sair do ar. Honestamente, o site sair do ar é o de menos. O que nós deveríamos estar nos preocupando com é que pessoas foram presas por "violação de direitos autorais".

Eu não sou necessariamente contra a restrição de uso comercial de obras copyrighted, então eu não necessariamente acho (por ora) que coisas como o Mega Filmes devam ser permitidas. O que eu definitivamente não acho certo é que alguém possa ser preso por violação de direito autoral, comercial ou não. Simplesmente não é aceitável que alguém vá para a cadeia por distribuir cópias de filmes, livros ou obras quaisquer. Se é para proibir "violação de direitos autorais", então que tirem o site do ar, cobrem multa (num valor razoável, não mil vezes o "valor" de cada cópia), whatever. Agora mandar alguém para a cadeia por distribuir cópias de obras publicadas é uma medida totalmente desproporcional à "gravidade" da situação (que é basicamente nenhuma).

Assim, eu vos peço humildemente para assinar a petição do Partido Pirata do Brasil para libertar os administradores e salvar o site.

Addendum: Mesmo que você ache que assinar a petição não vai levar a nada, mostrar que há bastante gente que se importa com a questão já é alguma coisa. Assumindo que haja.

1 comentário / comment

Words of wisdom by Tim Pulju

2015-09-06 20:58 -0300. Tags: life, mind, random, em-portugues

Tim: … I'm gonna pick number 3 … also because if I just agree with everyone else and we're all correct then I attain no glory from that, but if I disagree with everyone else and I'm correct then I'll have great glory, and I think I'll put it in my gravestone, so I'm gonna say that number 1 and number 2 are correct and number 3 is false.

[…]

Trey: Now, on to Tim's great glory, did you consider the possibility of great shame?

Tim: I did, but I've lived with great shame for so much of my life that it won't be no great burden to add further shame to myself.

Language Made Difficult, Vol. XVII

(By the way, o Tim Pulju apresentou uma TED talk mui bacaninha chamada The Uncanny Science of Linguistic Reconstruction em 2011.)

1 comentário / comment

Lisp stares at PHP

2015-09-02 22:11 -0300. Tags: comp, prog, pldesign, lisp, php, lows, in-english, em-portugues

[This post is also available in English.]

No último post, eu falei sobre o lows, um Lisp que compila para PHP 5.2 que eu comecei a desenvolver, e o que eu fiz até agora nele. Neste post, eu pretendo discutir algumas questões de design da linguagem, mais ou menos na mesma idéia da série Blueprints for a shell (só que bem menor (or so I hope)). Como já mencionei, não sei quando vou mexer no lows de novo (afinal, eu tenho certos afazeres mundanos, tais como um mestrado para terminar), mas deixo aqui documentadas algumas idéias. Comentários são sempre bem-vindos.

Tipos de dados

Os tipos de dados do PHP não casam muito bem (leia-se: não casam praticamente nada) com os tipos convencionais do mundo Lisp. O plano é tentar projetar a linguagem para permitir um estilo de programação suficientemente Lisp-like com os tipos existentes, ao invés de tentar recriar os tipos líspicos tradicionais, até porque isso não seria muito viável em termos de performance em PHP.

Arrays

Basicamente o único tipo de coleção que o PHP possui é o array, que faz tanto o papel de lista quanto de dicionário (e mesmo como dicionário, ele preserva a ordem das inserções). Eu não vejo nenhuma maneira eficiente de implementar um cons em PHP (adiciona um elemento a uma lista, criando uma lista nova, em espaço/tempo O(1)). Daria para criar uma classe Cons, mas o overhead seria muito grande, e além do mais o objetivo da linguagem é interagir facilmente com código PHP. So, arrays.

As operações map, filter, reduce e afins são independentes da representação da coleção e funcionam igualmente bem com arrays. O idioma de criar listas usando cons e recursão, por outro lado, cai por terra, mas ao mesmo tempo ele já não seria uma boa escolha em PHP porque chamadas de função são mais custosas, e não há tail call optimization. Na verdade tail call optimization não nos ajudaria nesse caso anyway, porque a recursão não fica in tail position; nessa situação, quando a performance é relevante, a galera costuma acumular os elementos da lista como um argumento da chamada recursiva e chamar reverse! no final do loop, mas se é para fazer isso, podemos passar um array como argumento e adicionar elementos com push. Anyway.

No futuro, map e afins podem ser inlined, o que torna essas construções basicamente equivalentes a loops. Até mesmo a closure passada como argumento para o map não precisa ser construída, e ao invés disso o corpo do lambda pode ser inserido diretamente no loop.

Há que se pensar na sintaxe equivalente ao Array(...) do PHP. Para listas simples, (array 1 2 3) é suficiente. O problema é o equivalente da forma Array("foo"=>1, "bar"=>2). Quando a chave é uma string literal simples, uma possibilidade seria usar keywords do Chicken: (array foo: 1 bar: 2). Quando a chave não é literal, algo como (array (=> key1 val1) (=> key2 val2)) poderia ser usado, mas isso é meio verboso. I don't know.

Uma bizarrice de arrays do PHP é que elas são passadas por cópia. Ao que parece, simplesmente não existe uma maneira de passar um array "by sharing", como os valores costumam ser passados em Lisp ou em Python. É possível criar uma referência (que na verdade é um alias) para uma variável que contém um array, mas a referência é à variável, não ao array; se outro valor é atribuído à variável, a referência/alias reflete a modificação. Acho que teremos que conviver com isso.

Símbolos

Não existe nada equivalente a símbolos em PHP. A principal vantagem de símbolos sobre strings é o fato de que eles podem ser comparados em tempo constante, mas o PHP não possui nada realmente equivalente ao operador eq dos Lisps, então acho que não teria muito propósito implementar símbolos em lows. 'foo poderia ser usado como abreviação para "foo", talvez.

Pharen tem um "operador" #foo, que gera a string correspondente ao nome da função foo. A razão desse operador é que Pharen converte hífens em nomes de função para underlines (o que é uma boa idéia em um Lisp→PHP), e o operador # produz uma string com o nome convertido. Talvez ' pudesse ser usado com o mesmo propósito em lows.

By the way, lows não tem um operador quote de verdade, pois o código de um programa lows não é representado por uma estrutura de dados de lows (i.e., do PHP). Yeah, lows não é uma linguagem homoicônica, e talvez nem merecesse o título de Lisp por conta disso. Macros em lows não seriam escritas em lows, mas sim em (Chicken) Scheme, mas veremos isso mais adiante.

(Qual seria então o significado de '(+ 1 2) em lows? Erro de sintaxe? Uma abreviação de (array '+ '1 '2)?)

Miscelânea

Não há muito o que dizer sobre os demais tipos (números, strings, booleanos, e NULL). PHP converte loucamente entre tipos, o que é meio desagradável, mas não há muito o que fazer, a menos que queiramos inserir checks antes do uso de qualquer operação. (Isso poderia ser uma opção de compilação, útil para debugging, talvez.)

Operadores

Operações aritméticas e afins em Lisp costumam ser funções, e não operadores especiais. Porém, não queremos ter que chamar uma função em lows para realizar operações aritméticas, pois o custo de uma chamada de função em PHP é alto. (Mesmo Lisps costumam otimizar essas chamadas quando é possível determinar os tipos dos argumentos em tempo de compilação.) Por outro lado, seria bom podermos passar + e afins como argumento para outras funções. Uma possível solução seria expandir '+ para (lambda (x y) (+ x y)), mas isso fixa o número de argumentos do operador, enquanto o ideal é que (+ 1 2 3) seja possível e seja traduzido para 1+2+3. Outra possibilidade seria ter tanto uma função, definida na biblioteca padrão, quanto um operador especial, e o compilador decidiria qual usar dependendo do caso. Mas não tem por que incluir uma função para cada operador na biblioteca padrão, se o próprio compilador pode gerar o lambda com o código apropriado automaticamente quando necessário.

A maioria dos operadores do lows seriam equivalentes diretos, e teriam o mesmo nome, dos operadores do PHP. As exceções seriam:

Os operadores binários têm um detalhe extra: x || y em PHP sempre retorna um booleano, enquanto em Lisp (or x y) costuma retornar x se ele é verdadeiro, e y caso contrário. Daria para traduzir (or x y) para x? x : y (com o detalhe de que x não pode ser avaliado duas vezes), mas isso meio que estraga a idéia de tradução direta (os condicionais de ifs teriam uma cara bizarra no código resultante). Talvez o compilador pudesse detectar quando o and/or está sendo usado como condicional de um if e traduzi-lo para &&/|| nesses casos.

De qualquer forma, o que o PHP acha que é verdadeiro é diferente do que um Lisp normalmente acha que é verdadeiro: em Lisp, normalmente o valor booleano falso (ou a lista vazia) é considerada como falso, e tudo o mais é considerado verdadeiro (inclusive 0, a string vazia, etc.). Esse comportamento inclusive faria mais sentido em PHP, que possui funções como strpos que devolvem um índice numa string (que pode ser 0) ou false. Mas não adiantaria mudar só o and, teria que mudar o comportamento do if também. Tem que ver isso aí...

Funções

Funções em PHP não verificam se o número de argumentos passados corresponde ao número de parâmetros declarados. Poderia haver uma opção de compilação para inserir um check no começo de cada função.

While we are at it, poderíamos permitir declarar os tipos dos argumentos e adicionar checks no começo da função, ou verificar e emitir warnings em tempo de compilação quando possível. Mas isso ficaria para o futuro distante.

Muito me alegraria ter keyword arguments em lows. Eles poderiam ser passados como um array, i.e., (f x y a: 1 b: 2) seria equivalente a (f x y (array a: 1 b: 2)), e no corpo da função referências a a seriam traduzidas para referências à chave no array.

Variáveis globais e constantes

O plano original era que um acesso a uma variável não declarada localmente seria interpretado como um acesso à variável global de nome correspondente. Porém, estive pensando se não seria melhor usar um prefixo ou outra convenção para nomear variáveis globais (e.g., *foo*, a la Common Lisp, ou $foo, a la Ruby). A vantagem seria poder detectar erros de digitação em tempo de compilação, ao invés de assumir que o nome não declarado se trata de uma variável global.

Outro problema são as constantes globais. O Pharen aparentemente decide se um nome é uma constante ou uma variável exigindo que constantes sejam escritas em maiúsculas. ($_SERVER e afins também são escritos em maiúsculas, mas o Pharen usa um operador especial $ para acessar essas variáveis.) Nada no PHP exige que o nome de uma constante seja totalmente em maiúsculas, entretanto. Uma possibilidade seria usar +nome+, uma convenção usada por alguns programadores Common Lisp. I don't know. (error-reporting +E-ALL+)? Talvez (error-reporting #E_ALL)? (error-reporting <E_ALL>)?

Namespaces

Um dialeto de Lisp é classificado como Lisp-1 ou Lisp-2 dependendo de se nomes de funções e de variáveis vivem em um mesmo namespace (Lisp-1), ou se dois namespaces separados são usados (Lisp-2). PHP é mais próximo de um Lisp-2 do que de um Lisp-1, mas a distinção não é bem a mesma, pois em PHP variáveis possuem um prefixo que as identifica ($), enquanto em Lisp a posição do nome em uma chamada determina o namespace a ser utilizado (se o nome aparece como primeiro elemento em uma lista entre parênteses, ele é um nome de função; caso contrário, é uma variável).

Para o lows ser um Lisp-1, seria necessário determinar se (f x y) deveria ser traduzido para f($x, $y) ou $f($x, $y). No geral, isso não é possível (o compilador lows não tem conhecimento dos nomes definidos fora do arquivo que está compilando). Assim, um Lisp-2 é uma escolha mais natural: (f x y) sempre traduz para f($x, $y). Para obter o equivalente de $f($x, $y), i.e., para usar uma variável como a função a ser chamada, deve-se usar um operador especial, normalmente chamado funcall nos Lisps: (funcall f x y).

Em PHP 5.2, $f() só funciona se $f é uma string contendo o nome da função a ser chamada; não é possível chamar closures criadas com (lambda ...) dessa maneira. Por outro lado, call_user_func($f, ...) funciona tanto quando $f é uma string quanto com outros valores chamáveis. Como queremos que funcall funcione em ambos os casos, funcall deve ser traduzido para call_user_func.

A situação contrária ao funcall, i.e., quando se quer usar uma função nomeada como argumento para outra, é coberta pelo nosso operador 'foo, que produz uma string com o nome (convertido) da função foo. Como PHP não possui funções de primeira classe nem declarações locais de função, a string com o nome da função é suficiente para identificar a que função está se referindo.

Classes, métodos e bagulheiras

Definições de classes e métodos são relativamente straightforward. (defclass Foo atributos/métodos...) não tem por que ser muito diferente da construção equivalente em PHP. Podemos adicionar algumas abreviações, e.g., permitir definir um construtor padrão ao invés do costumeiro $this->x = $x; $this->y = $y; … do PHP.

A bagunça começa na hora de escolher uma sintaxe para chamada de métodos e acesso a atributos. Pharen usa algo como (-> objeto (método args...)), mas isso é meio verboso. Uma solução seria permitir (objeto->método args...) como uma abreviação, no caso mais comum em que objeto é uma variável, mas eu prefiro . para isso ao invés de ->. By the way, e se eu quiser um método cujo nome está em uma variável, i.e., $objeto->$método(args...)? Suponho que em Pharen seja possível escrever (-> objeto ($método args...)), que é como eles resolvem o problema do funcall, mas meh, não é assim que lows funciona – e se eu quiser uma expressão ao invés de uma variável para computar o nome do método? Que tal (-> objeto (funcall método args...))? Mas agora eu vejo que toda essa sintaxe conflita com a sintaxe para acesso de atributos. Em Pharen, (-> objeto nome) é equivalente a $objeto->nome. Novamente, para usar um nome vindo de uma variável, pode-se usar $nome, mas, novamente, isso não permite usar uma expressão para calcular o nome do atributo; (-> objeto (computa-nome)) seria interpretado como $objeto->computa_nome().

So. Estou com a sensação de que o adequado em lows seria que $obj->nome fosse algo como (-> obj 'nome), com o nome quoted. Sem o quote, nome seria avaliado como uma expressão que produz um nome. Quanto a chamada de métodos, talvez o jeito seja usar um operador distinto do usado para atributos. Meh. Independentemente dos operadores escolhidos, obj.nome seria considerado uma abreviação para (-> obj 'nome), já que acredito que acesso com um nome fixo a um objeto em uma variável pré-determinada seja o caso mais comum. E (obj.método args...) seria uma abreviação para $obj->método(args...). Isso tudo requer mais pensação.

Analogamente, o argumento do operador new também deveria ser quoted: (new 'Foo), não (new Foo). Nesse sentido, as operações do lows seriam mais parecidas com o make-instance e slot-value do CLOS do que com o Pharen. Now, o CLOS usa (método objeto args...) para chamadas de métodos (que na verdade são multi-métodos; o objeto é um argumento como os demais, que tambem podem ser especializados para diferentes classes). O problema de adotar essa sintaxe em lows é que é necessário saber que método é um método e não uma função para decidir que código PHP gerar. Uma possiblidade seria usar (.método objeto args...). Tem que ver isso aí. Outra situação a ser considerada é quando queremos passar um método como argumento para uma função (i.e., o equivalente do Array($obj, "método") do PHP). Hmmrgh...

Falando de abreviações, acho que seria uma boa adotar @nome como uma abreviação de $this->nome, a la Ruby. (@método args...) poderia ser abreviação de $this->método(args...).

[Um problema de usar (. objeto 'nome) é que . é sintaxe especial em Scheme, e atualmente eu uso a função read do Scheme para ler o código-fonte lows de um arquivo. Mais adiante, é de se pensar escrever um reader próprio para o lows. Isso também teria a vantagem de permitir manter informação de linha e coluna nas formas lidas.]

Macros

Como o compilador é escrito em Chicken Scheme, e não PHP (e nem por sonho eu pensaria em escrevê-lo em PHP (embora talvez reescrevê-lo em lows no futuro não seja algo de se descartar)), e não possui um interpretador próprio, ele não é capaz de rodar código lows em tempo de compilação. Isso exclui a possibilidade de macros procedurais nativas em lows. O meu plano, por ora, é permitir que macros em Scheme possam ser escritas, pois essas podem ser executadas pelo compilador. Por um lado é bizarro (em um Lisp) escrever macros em uma linguagem diferente da linguagem de programação, mas por outro isso tem a vantagem de permitir usar todas as funções do Chicken na geração de código. Uma outra possibilidade (não-exclusiva) é ter um sistema de macros baseado em casamento de padrões, como o syntax-rules do Scheme, que não requer a execução de código lows em tempo de compilação.

Além das macros que recebem código lows e produzem código lows, também seria interessante ter uma construção para emitir código PHP diretamente, similar ao asm do GCC e afins. No caso mais simples, a construção seria usada com uma string constante, mas nada impediria que expressões arbitrárias em Scheme pudessem ser usadas para gerar o código PHP.

Enough talk

Acho que era isso por enquanto. Sugestões, opiniões, etc., são bem-vindos.


[Version in English follows.]

In the last post, I talked about lows, a Lisp which compiles to PHP 5.2 which I started to develop, and what I got done so far. In this post, I intend to discuss some design questions of the language, more or less in the same spirit of the Blueprints for a shell series (in Portuguese) (except much shorter (or so I hope)). As I mentioned, I don't know when I'm going to work on lows again (after all, I've got some mundane tasks to do, such as a Master's to finish), but I'll leave some ideas documented here. Comments are always welcome.

Data types

The PHP data types don't match very well (read: mostly don't match) the conventional types from the Lisp world. The plan is to try to design the language to enable a sufficiently Lisp-like programming style with the existing types, rather than trying to recreate the traditional Lispy types in PHP, even more because that would not be very feasible in terms of performance in PHP.

Arrays

Basically the only type of collection PHP has is the array, which fills the roles of both lists and dictionaries (and even as a dictionary, it preserves insertion order). I don't see any efficient way to implement cons in PHP (adds an element to a list, creating a new list, in space/time O(1)). It would be possible to create a Cons class, but the overhead would be too large, and moreover the goal of the language is to interact easily with PHP code. So, arrays.

The map, filter, reduce and similar operations are independent of the representation of the collection and work equally well with arrays. The idiom of building lists using cons and recursion, on the other hand, is right out, but at the same time it wouldn't be a good choice in PHP anyway because function calls are more costly, and there is no tail call optimization. Actually, tail call optimization wouldn't help in this case anyway, because the recursion is not in tail position; in this situation, when performance matters, people usually accumulate the elements of the list in an argument to the recursive call, and call reverse! at the end of the loop, but if we're going to do that, we can equally well pass an array as the argument and accumulate the elements with push. Anyway.

In the future, map and company can be inlined, which makes these constructions basically equivalent to loops. Even the closure passed as an argument to map does not need to be created, and instead the body of lambda can be inserted directly in the loop.

We have to think about the syntax equivalent to PHP's Array(...). For simple lists, (array 1 2 3) works. The problem is the equivalent of the form Array("foo"=>1, "bar"=>2). When the key is a simple literal string, one possibility would be to use Chicken keywords: (array foo: 1 bar: 2). When the key is not a literal, something like (array (=> key1 val1) (=> key2 val2)) could be used, but that is somewhat verbose. I don't know.

One oddity of PHP arrays is that they are passed by copy. Apparently, there simply is no way to pass an array "by sharing", as values are usually passed in Lisp or Python. It is possible to create a reference (actually an alias) to a variable containing an array, but the reference is to the variable, not to the array; if another value is assigned to the variable, the reference/alias will reflect the modification. I guess we'll have to live with that.

Symbols

There is nothing equivalent to symbols in PHP. The main advantage of symbols over strings is that they can be compared in constant time, but PHP doesn't really have anything equivalent to the Lisp eq operator, so I think there wouldn't be much to gain in implementing symbols in lows. 'foo could be used as an abbreviation for "foo", perhaps.

Pharen has an "operator" #foo, which yields the string corresponding to the name of the function foo. The reason for that operator is that Pharen converts hyphens in function names to underscores (which is a good idea in a Lisp→PHP), and the # operator yields a string with the converted name. Perhaps ' could be used to the same purpose in lows.

By the way, lows does not have a true quote operator, as the code of a lows program is not represented by a lows (i.e., PHP) data structure. Yeah, lows is not a homoiconic language, and perhaps doesn't even deserve the name "Lisp" because of that. Macros in lows wouldn't be written in lows, but rather in (Chicken) Scheme, as we'll see later.

(What would be the meaning of '(+ 1 2) in lows? Syntax error? Shorthand for (array '+ '1 '2)?)

Miscellaneous

There isn't much to say about the remaining types (numbers, strings, booleans, and NULL). PHP converts nilly-willy between types, which is somewhat annoying, but there is not much we can do, unless we want to insert checks before every operation. (This could be a compilation option, useful for debugging, perhaps).

Operators

Arithmetic operations and the like in Lisp are usually functions, not special operators. However, we don't want to have to call a function in lows to perform arithmetic operations, because the cost of a function call in PHP is high. (Even Lisps usually optimize these calls away when it is possible to determine the types of the operands at compile time.) On the other hand, it would be nice to be able to pass + and the like as arguments to other functions. One possibility would be to expand '+ to (lambda (x y) (+ x y)), but that would fix the number of arguments of the operator, whereas ideally (+ 1 2 3) should work and translate to 1+2+3. Another possibility would be to have both a function, defined in the standard library, and the special operator, and the compiler would decide which to use depending on the situation. But there is no reason to include a function for each operator in the standard library, when the compiler can automatically generate the lambda with the appropriate code as needed.

Most lows operators would be directly equivalent, and would have the same name, as the PHP operators. The exceptions would be:

The binary operators have an extra detail: x || y in PHP always returns a boolean, whereas in Lisp (or x y) usually returns x if it is true-ish, and y otherwise. It would be possible to translate (or x y) to x? x : y (taking extra care not to evaluate x twice), but that kinda messes with the idea of straightforward translation (the conditions of if blocks would look weird in the compiled code). Maybe the compiler could detect when and/or are being used as the condition of an if and translate them to &&/|| in those cases.

In any case, what PHP thinks is true is at odds with what Lisps usually think is true: in Lisp, usually the false boolean value (or the empty list) is considered false, and everything else is considered true (including 0, the empty string, etc.). This behaviour would actually make more sense in PHP, which has functions such as strpos which return an index into a string (which may be 0) or false. But then it wouldn't be enough to change the behavior of and, we'd have to change the behavior of if too. Gotta think about it.

Functions

PHP functions don't check if the number of arguments in a call match the number of declared parameters. There could be a compilation option to insert a check at the beginning of each function.

While we are at it, we could allow declaring the types of the parameters and add checks at the beginning of the function, or verify and emit warnings at compile time if possible. But that would be left for the distant future.

It would much gladden me to have keyword arguments in lows. They could be passed in an array, i.e., (f x y a: 1 b: 2) would be equivalent to (f x y (array a: 1 b: 2)), and in the function body references to a would be translated to array dereferences.

Global variables and constants

The original plan was that an access to a variable not declared locally would be interpreted as an access to the global variable with that name. However, I have been thinking if it wouldn't be better to use a prefix or some other convention to name global variables (e.g., *foo* as in Common Lisp, or $foo as in Ruby). The advantage would be to be able to detect mistyped variable names at compile time, rather than assuming that the undeclared name refers to a global.

Another problem is global constants. Pharen seems to decide if a name is a constant or a variable by requiring all constants to have all-uppercase names. ($_SERVER et al. are all-uppercase too, but Pharen uses a special operator $ to access those.) Nothing in PHP requires that the name of a constant be all-uppercase, though. A possibility would be to use +name+, a convention used by some Common Lisp programmers. I don't know. (error-reporting +E-ALL+)? Maybe (error-reporting #E_ALL)? (error-reporting <E_ALL>)?

Namespaces

Lisp dialects are usually classified as Lisp-1 or Lisp-2 depending on whether function and variable names live in a single namespace (Lisp-1), or if there are two separate namespaces for them (Lisp-2). PHP is closer to a Lisp-2 than a Lisp-1, but the distinction is not quite the same, because in PHP variable names have a prefix identifying them ($), whereas in Lisp the position of the name in a call determines which namespace is to be used (if the name appears as the first element of a parenthesized list, it is a function name; otherwise, it's a variable name).

For lows to be a Lisp-1, it would be necessary to determine if (f x y) should be translated to f($x, $y) or $f($x, $y). In general, this is not possible (the lows compiler has no knowledge of the global names defined outside the file being compiled). Therefore, a Lisp-2 is a more natural choice: (f x y) always translates to f($x, $y). To get the equivalent of $f($x, $y), i.e., to use a variable as a function to be called, a special operator must be used, usually named funcall in Lisps: (funcall f x y).

In PHP 5.2, $f() only works if $f is a string containing the name of the function to be called; it is not possible to call closures created with (lambda ...) in this way. On the other hand, call_user_func($f, ...) works equally well when $f is a string and with other callable values. Since we want funcall to work in both cases, funcall must be translated to call_user_func.

The opposite situation to funcall, i.e., when we want to pass a named function as an argument to another, is covered by our ' operator, which yields a string with the (converted) name of the function foo. Since PHP does not have first-class functions or local function declarations, a string with the function name is enough to identify which function is being referenced.

Classes, methods, and stuff

Class and method definitions are relatively straightforward. (defclass Foo attributes/methods...) doesn't have to be very different from the equivalent construction in PHP. We can add some shorthands, e.g., allow a default constructor instead of the usual $this->x = $x; $this->y = $y; … in PHP.

The mess begins when choosing a syntax for method calls and attribute access. Pharen uses something like (-> object (method args...)), but that is somewhat verbose. A solution would be to allow (object->method args...) as a shorthand for the most common case when object is a variable, but I prefer . for this instead of ->. By the way, what if I want to use a method whose name is in a variable, i.e., $object->$method(args...)? I suppose Pharen allows (-> object ($method args...)), which is how they solve the funcall problem, but meh, that's not how lows works – what if I want an expression instead of a variable to compute the method name? What about (-> object (funcall method args...))? But now I see that all this syntax conflicts with the syntax for attribute access. In Pharen, (-> object name) is equivalent to $object->name. Again, to use a name from a variable, it allows $name, but, again, this does not allow using an expression to compute the name of the attribute; (-> object (compute-name)) would be interpreted as $object->compute_name().

So. I have the feeling that the appropriate thing in lows would be that $obj->name should be something like (-> obj 'name), with the name quoted. Without the quote, name would be evaluated as an expression yielding a name. As for method calls, perhaps the solution is to use a distinct operator from that used for attribute access. Meh. Regardless of the operators chosen, obj.name would be considered shorthand for (-> obj 'name), as I think access with a fixed name to an object in a known variable is the most common case. And (obj.method args...) would be shorthand for $obj->method(args...). All of this requires more thinking.

Analogously, the argument for the new operator should also be quoted: (new 'Foo), not (new Foo). In this sense, lows's operations would be more similar to make-instance and slot-value from CLOS than to Pharen. Now, CLOS uses (method object args...) for method calls (which are actually multi-methods; object is an argument just like the others, which can also be specialized for different classes). The problem of adopting this syntax in lows is that it requires knowing that method is a method and not a function to decide which PHP code to emit. One possibility would be using (.method object args...). Gotta think about that. Another situation to be considered is when we want to pass a method as an argument to a function (i.e., the equivalent of PHP's Array($obj, "method")). Hmmrgh...

Speaking of shorthand, I think it would be a good idea to adopt @name as shorthand for $this->name, a la Ruby. (@method args...) could be shorthand for $this->method(args...).

[One problem of using (. object 'name) is that . is special syntax in Scheme, and currently I use Scheme's read function to read lows source code from a file. Later on, one might think about writing a proper reader for lows. That would also have the advantage of allowing the compiler to keep track of line and column information.]

Macros

Because the compiler is written in Chicken Scheme, and not PHP (and I wouldn't even dream of writing it in PHP (though perhaps rewriting it in lows someday is not entirely unthinkable)), and has no lows intepreter, it is not capable of running lows code at compile time. This excludes the possibility of native procedural macros in lows. My plan, for now, is to allow writing macros in Scheme, as those could be run by the compiler. On the one hand it is somewhat weird (for a Lisp) to write macros in a different language, but on the other hand this has the advantage of allowing one to use all Chicken functions in code generation. Another (non-mutually-exclusive) possibility is to have a macro system based on pattern matching, like Scheme's syntax-rules, which does not require running lows code at compile time.

Beside macros which take lows code and yield lows code, it would also be interesting to have a construction to emit PHP code directly, similar to asm in GCC and the like. In the simplest case, the construction would be used with a constant string, but nothing would exclude using arbitrary Scheme expressions to generate PHP code.

Enough talk

I think that's it for now. Suggestions, opinions, etc., are welcome.

21 comentários / comments

Lisp meets PHP

2015-09-02 04:25 -0300. Tags: comp, prog, pldesign, php, lisp, lows, in-english, em-portugues

[This post is also available in English.]

Como eu andei comentando por aí, eu comecei a implementar uma linguagem Lisp-like que compila para PHP 5.2, chamada lows. Não sei quando vou mexer nesse projeto de novo, mas deixo aqui algumas notas para o meu eu futuro e para quem tiver interesse.

Prelúdio

Tudo começou no domingo retrasado, quando eu resolvi dar uma mexida no blog system, for a change. Mexer no blog sempre é uma experiência ambivalente, pois por um lado tem uma porção de idéias que eu gostaria de implementar nele, mas por outro lado eu tenho que fazer isso em PHP, porque é a única coisa que roda no inf.ufrgs.br (e eu não pretendo pagar por hospedagem any time soon). Eu comentei com uma pessoa que se eu tivesse a opção, eu já teria reescrito o blog em Scheme há muito tempo. Ela me perguntou por que eu não fazia algo para rodar Scheme em PHP, e eu comentei que já tinha pensado em fazer um compilador de Lisp para PHP, mas que achava que era muita mão só para poder escrever o blog em Lisp. Assim, eu segui meu domingo fuçando no blog em PHP.

Depois de uma porção de gambiarras e mais uma porção de concessões às bizarrices do PHP (e.g., existem os métodos mágicos __call, __callStatic e __get, mas não existe um __getStatic, sabe-se lá por quê), eu consegui reescrever a parte do blog responsável por mensagens multilíngües de uma maneira que me agradasse, e até já não estava mais achando tão horrível escrever o código em PHP.

No final do dia, depois de ter testado o código no meu servidor local, eu resolvi fazer upload da nova versão para o inf.ufrgs.br. Para minha surpresa, o PHP começou a reportar uma porção de erros no código. Turns out que a versão do PHP que roda no inf.ufrgs.br é a "5.2.6-1+lenny16". Para quem não sabe, lenny é o codinome do Debian 5. O Debian 5 foi lançado em 2009 e não recebe mais atualizações desde 2012. Três releases estáveis do Debian saíram desde então (as releases estáveis do Debian saem a cada mais ou menos dois anos). Meanwhile, eu estava rodando PHP 5.6.12 em um Debian testing em casa, e praticamente todo o código que eu tinha escrito usava features introduzidas no PHP 5.3.

Depois de tentar sem muito sucesso mudar um pouco o código para ver se conseguia fazê-lo rodar no PHP 5.2, eu resolvi largar de mão e deixar para mexer no código outro dia. Porém: (1) eu não estava a fim de enfeiar o código só para fazê-lo rodar em um PHP velho; (2) more generally, eu não estava a fim de mexer em PHP de novo; e (3) eu não estava conseguindo dormir aquele dia. Conclusão: comecei a escrever um tradutor Lisp→PHP, primariamente for the lol. Mais uma noite mal-dormida, e eis que eu tinha um tradutor (ou compilador, como preferir) Lisp→PHP que fazia pouca coisa, mas o suficiente para me convencer de que a idéia era pelo menos viável. Nasceu assim o lows, ou Lisp for Old Web Servers.

Idéia

A idéia do projeto é criar uma linguagem Lisp-like que satisfaça os seguintes objetivos:

Compilar para PHP 5.2. A idéia é eu poder rodar o código resultante no inf.ufrgs.br (e idealmente escrever a próxima versão do blog em lows), então eu preciso "targetar" especificamente PHP 5.2. Eu também podia tentar convencer a galera da admrede a atualizar o servidor, mas (1) acho pouco provável que isso aconteça any time soon, e eu não estava a fim de esperar; (2) a essa altura eu já tinha tomado a limitação a PHP 5.2 como um desafio (lembre-se de que eu comecei o projeto para matar tempo enquanto o sono não vinha); (3) já existe um projeto similar, chamado Pharen, que targeta PHP 5.5, e eu queria um diferencial (a.k.a. desculpa) para justificar o meu projeto.

Gerar código PHP relativamente straightforward. Tanto quanto possível, o código PHP resultante da compilação deve ser uma tradução mais ou menos direta do código lows original. A idéia é facilitar a depuração (e em particular a tarefa de encontrar o código lows correspondente a um erro reportado no código PHP), e também a esperança de que quanto mais direto for o código resultante, menor o impacto na performance de escrever o código em lows ao invés de diretamente em PHP.

Integrar facilmente com PHP. Deve ser possível usar funções, classes, etc. do PHP a partir de código lows e vice-versa, sem necessidade de conversões, anotações e afins.

Manter uma essência Lisp-like. A idéia não é simplesmente criar um redressing de PHP em S-expressions, mas sim uma linguagem que permita programar em um estilo semi-funcional e "Lispy" e evite as bizarrices do PHP na medida do possível (ao mesmo tempo em que introduz outras bizarrices).

Esse conjunto de objetivos influencia tanto a implementação (que deve gerar um PHP relativamente limpo/direto) quanto o design da linguagem (que não deve fugir muito do PHP para permitir a tradução relativamente direta e a compatibilidade com código PHP).

Transformando Lisp em PHP

Expressões e statements

Um desafio que eu encontrei logo no começo é o fato de que o PHP faz uma distinção entre expressões e statements, que (mostly) não existe em Lisp. Em particular, coisas como if, let (declaração de variáveis locais) e progn (executa uma seqüência de expressões e retorna o valor da última, mais ou menos análogo a um bloco entre chaves em PHP, mas que produz um valor) são expressões em lows. O if em princípio até poderia ser traduzido para o operador ternário (test? then : else), e o let poderia ser mais-ou-menos contornado já que atribuição é uma expressão em PHP. O problema é que PHP não tem um operador vírgula como em C. Coisas como:

(+ 1 (let ((x 23)
           (y 42))
       (* x y)))

não possuem uma tradução direta para PHP, pois não é possível escrever 1 + ($x=23, $y=42, $x*$y). Uma solução gambiarrenta seria gerar:

1 + ((($x=23) || TRUE) ? ((($y=42) || TRUE) ? ($x*$y)
                                            : whatever)
                       : whatever)

o que simula o operador vírgula usando só um branch do operador ternário, mas: (1) isso não funciona no caso geral (em particular, se uma das expressões é um progn contendo um echo ou alguma outra coisa statementosa); (2) isso vai totalmente contra a idéia de gerar código straightforward. A solução é mover as atribuições para antes da soma, mas, no caso geral, só mover qualquer coisa que não seja uma expressão em PHP para antes da expressão não é suficiente: se os branches de um condicional contêm expressões com efeitos colaterais, não é possível movê-las para fora do condicional. Por exemplo, em algo como:

(defun print-and-return (val)
  (echo "O valor é " val "\n")
  val)

(+ 1 (if (> x y)
         (let ((a (print-and-return (- x y))))
           (* a a))
       0))

não é possível traduzir a soma para:

$a = print_and_return($x-$y);
1 + (($x>$y)? ($a*$a) : 0)

pois print_and_return não pode ser chamada antes que o teste $x>$y seja realizado.

[A essa altura talvez lhe ocorra (como me ocorreu) o pensamento: "Ok, e por que a gente simplesmente não proíbe expressões complexas desse tipo aninhadas em outras expressões? Quando é que eu vou usar isso anyway?" Mas esse é justamente o tipo de limitação tosca de que nós estamos tentando fugir criando uma nova linguagem ao invés de programar em PHP! "Do not tell me “that’s what you get for doing weird things”. If two features exist, someday, someone will find a reason to use them together."]

A solução que eu encontrei foi traduzir o (if ...) para um bloco if em PHP, armazenar o valor do if em uma variável temporária, e usar a variável temporária na soma. O exemplo anterior fica algo como:

if ($x>$y) {
    $a = print_and_return($x-$y);
    $tmp = $a*$a;
} else {
    $tmp = 0;
}

1 + $tmp

Isso significa que para traduzir uma expressão como (+ ...), pode ser necessário emitir blocos de código antes da tradução da soma propriamente dita. Conseqüentemente, a função de tradução não pode ser simplesmente algo como:

translate[(+ lhs rhs)] = translate[lhs] + translate[rhs]

pois tanto a tradução de lhs quanto de rhs podem requerer a inserção de blocos de código antes da soma (e a soma, por sua vez, pode estar aninhada em outra expressão).

A solução que eu encontrei para esse problema foi fazer as funções de tradução retornarem dois valores: a expressão equivalente em PHP, e uma lista de "efeitos", que são basicamente (mas não necessariamente) instruções para emitir código nas redondezas da tradução. Por exemplo, a função de tradução aplicada ao if do exemplo gera a expressão $tmp (que pode ser inserida no meio de outra expressão que usa o valor do if), e o efeito (EmitBefore bloco-if-em-PHP), que indica que o bloco-if-em-PHP deve ser inserido antes da expressão que contém o if na geração do código PHP. Como a inserção só pode ser realizada fora de uma expressão, o efeito é propagado pelas funções de tradução de expressões, até que ele chega em uma função que emite statements (e.g., o corpo de um bloco if do PHP, ou o corpo de uma função) e pode então ser emitido. Pseudocodiciosamente (oops, hmm):

translate[(+ lhs rhs)] =
   let
       lhs-trans; lhs-effects = translate[lhs]
       rhs-trans; rhs-effects = translate[rhs]
   in
       lhs + rhs; lhs-effects ++ rhs-effects


translate-statement[item] =
   let
       item-trans; item-effects = translate[item]
   in
       (código correspondente aos EmitBefore em item-effects) ++ item-trans ;
       (efeitos em item-effects excluindo os EmitBefore já processados)

O mesmo mecanismo pode ser usado para emitir código em outras situações (e.g., no caso do lambda, como veremos adiante), ou para coletar e propagar informações durante a tradução. Por exemplo, quando uma variável x que não possui declaração visível é usada, é emitido um efeito (Global x). A função que traduz o corpo de uma função coleta esses efeitos para gerar declarações do tipo global $x; no começo da função.

lambda

O próximo desafio foi traduzir o lambda para PHP. PHP >=5.3 possui closures (meio toscas – é necessário declarar explicitamente que variáveis são capturadas pela closure – mas elas existem), mas PHP 5.2 não. A próxima coisa que eu pensei foi usar uma classe "callable" com um método mágico __invoke, mas turns out que classes chamáveis só foram introduzidas em PHP 5.3 também. Porém, as funções que aceitam coisas chamáveis em PHP, como call_user_func e usort, aceitam arrays da forma Array(objeto, nome-de-método) como chamáveis. Pois, aí está algo que o lambda pode retornar.

Capturar as variáveis em uma closure mostrou-se bem mais fácil do que eu antecipava, graças às referências do PHP. Uma closure em lows é representada por uma classe com um membro/slot/propriedade/atributo/whatever para cada variável capturada. Quando a classe é instanciada, as variáveis são passadas por referência para o construtor. Dentro do corpo do lambda, referências a variáveis capturadas x são traduzidas para $this->x; como $this->x foi inicializado com uma referência ao $x capturado, o corpo do lambda vê a mesma variável $x através do atributo, inclusive refletindo modificações à mesma.

Como exemplo, algo como:

(defun adder (x)
  (lambda (n)
    (+ x n)))

(defun main ()
  (let ((f (adder 10)))
    (call_user_func f 5)))

vira algo como:

class Closure1 {
    function __construct(&$x) {
        // Captura de variáveis.
        $this->x = &$x;
    }

    function __invoke($n) {
        // Corpo do lambda.
        return $this->x + $n;
    }
}

function adder($x) {
    // Cria a closure, passando a variável a ser capturada para o seu construtor,
    // e retorna um valor que, quando chamado, chama o método "__invoke" da closure.
    return Array(new Closure1($x), "__invoke");
}

function main() {
    $f = adder(10);
    return call_user_func($f, 5);
}

E assim, o PHP e suas referências nos surpreendem positivamente (o que é uma surpresa in itself).

By the way, o mecanismo de efeitos aqui é usado para duas coisas: (1) a geração da classe antes da função que contém o lambda é feita propagando um efeito (EmitBeforeToplevel definição-da-classe); (2) cada referência a uma variável externa ao lambda gera um efeito (CapturedVar x); esses efeitos são coletados pela função que traduz o lambda para saber que atributos devem ser inicializados na classe e que argumentos devem ser passados ao construtor. Quando eu criei a treta dos efeitos eu não tinha pensado em todas essas aplicações, então mui me alegrou descobrir que eu podia reusar o mecanismo para essas coisas.

Name clashes

Em PHP, variáveis locais têm como escopo a função inteira onde se encontram, não apenas o bloco onde foram declaradas. Conseqüentemente, em código como:

(let ((x 23))
  (echo "x aqui vale 23: " x "\n")
  (let ((x 42))
    (echo "x aqui dentro vale 42: " x "\n"))
  (echo "x aqui fora ainda vale 23: " x "\n"))

não se pode usar o mesmo nome para as duas variáveis x na tradução, pois a definição mais interna de x sobrescreveria a mais externa. A solução e renomear uma das (ou ambas as) variáveis. O ideal seria fazer o mínimo de renomeações possível, para facilitar a leitura e depuração do código resultante. Porém, a implementação atual simplesmente renomeia todas as variáveis (adicionando um prefixo _número_), já que testar quando uma variável deve ser renomeada não é muito simples. Essa decisão não é local: mesmo não havendo nenhuma variável x visivelmente declarada no ponto onde ocorre um (let ((x 23)) ...), ainda assim é necessário renomear o x se em um ponto posterior da função uma variável global x for referenciada.

O algoritmo de renomeação / geração de nomes temporários assume que nomes iniciados por _número_ são reservados para o compilador. Acredito que isso não seja um problema na prática. (Para o caso de variáveis locais, uma variável _42_ vai ser renomeada para algo como _1__42_ de qualquer forma.) Um problema mais sério dessa abordagem é no escopo global, em particular nos nomes gerados para as classes que implementam closures (e.g., _1_Closure), pois esses nomes podem conflitar com closures criadas em outros arquivos (e.g., quando os resultados da tradução de múltiplos arquivos são incluídos com include em um programa PHP). Talvez uma solução seja incluir o nome do arquivo no nome da classe, ou gerar um hash a partir do código da closure (mas isso ainda gera conflito se um lambda idêntico aparece em outro arquivo), or something. I don't know. Também seria bom se o nome da classe fosse informativo o suficiente para indicar de onde saiu a definição no código original (e.g., _1_Closure_arquivo_função_linha). [Side remark: namespaces não existem em PHP 5.2.]

Um conflito de variável mais sutil é quando um let é executado múltiplas vezes e um lambda captura uma variável definida pelo let. Por exemplo, supondo a existência de uma construção while:

(let ((array-of-lambdas (array))
      (i 0))
  (while (< i 5)
    (let ((n 0))
      (array_push array-of-lambdas
                  (lambda ()
                    (set! n (+ n 1))
                    (echo n))))
    (set! i (+ i 1))))

Isso seria traduzido para algo como:

Class _1_Closure {
    function __construct(&$n) {
        $this->n = &$n;
    }

    function __invoke() {
        $this->n = $this->n + 1;
        echo $this->n;
    }
}

$array_of_lambdas = Array();
$i = 0;
while ($i < 5) {
    $n = 0;
    array_push($array_of_lambdas,
               Array(new _1_Closure($n), "__invoke"));
    $i = $i + 1;
}

O problema é que todas as iterações do loop usam a mesma variável $n, que é passada por referência ao construtor da closure; o correto seria cada iteração capturar um $n diferente. A solução é emitir uma chamada a unset($n) no final do while, de maneira que cada iteração crie uma variável nova, mas eu ainda não implementei isso.

PHP formatado

Um dos objetivos do projeto é gerar PHP legível, e isso envolve gerar código com indentação adequada. Depois de alguns false starts (na versão inicial, as funções de tradução geravam strings de código PHP diretamente, e a minha idéia original era usar caracteres especiais do ASCII como indicadores de "increase indentation" e "decrease indentation" quando o código fosse impresso, mas eu me dei conta de que não dava para escolher caracteres para isso porque qualquer caractere pode aparecer em uma string; além disso, misturar geração de código e questões de formatação estava ficando um bocado desagradável), eu resolvi fazer as funções de tradução gerarem estruturas representando árvores de sintaxe abstrata (ASTs) de PHP. Depois da tradução, as árvores são passadas a uma função print-php que trata dos detalhes sórdidos de imprimir o código com quebras de linha, indentações, espaços e parênteses nos lugares apropriados. Separation of concerns FTW.

O futuro

Como o post ficou grande, e eu deveria ir dormir, ficaremos por aqui. Em um post futuro, pretendo falar de algumas features que falta implementar, tais como classes, chamadas de métodos e demais firulas orientadas a objetos, bem como as decisões de design mais tricky (que eram o objetivo inicial do post, mas enfim). Quem tiver interesse, pode dar uma olhada no código no GitHub.


[English version follows.]

As I have been talking about, I started implementing a Lisp-like language which compiles to PHP 5.2, called lows. I don't know when I'm going to work on this project again, but I'll leave here some notes for my future self and whoever might be interested.

Prelude

It all began last last Sunday, when I decided to play with my blog system, for a change. Working on the blog system is always an ambivalent experience, because on the one hand there is a bunch of ideas I would like to implement in it, but on the other hand I have to do it in PHP, as that is the only thing that runs at inf.ufrgs.br (and I don't plan to pay for hosting any time soon). I commented to a person that if I had the choice, I would have rewritten the blog in Scheme long ago. She asked my why I didn't make something to run Scheme in PHP, and I said I had already though of writing a compiler from Lisp to PHP, but that I thought it was too much work just to be able to write the blog in Lisp. So, I went on with my Sunday messing with the blog in PHP.

After a number of kludges and another number of concessions to the oddities of PHP (e.g., there are the __call, __callStatic and __get magic methods, but no __getStatic, who knows why), I succeeded in rewriting the part of the blog responsible for multilingual messages in a way that pleased me, and I was even not finding it so horrible to write the code in PHP.

At the end of the day, after having tested the code in my local server, I decided to upload the new version to inf.ufrgs.br. To my surprise, PHP started reporting lots of errors in the code. It turns out that the version of PHP running at inf.ufrgs.br is "5.2.6-1+lenny16". For those who don't know, lenny is the codename of Debian 5. Debian 5 was launched in 2009 and does not get updates since 2012. Three stable Debian releases have been out since then (the stable releases of Debian are launched more or less every two years). Meanwhile, I was running PHP 5.6.12 in a Debian testing at home, and practically all the code I had written used features introduced in PHP 5.3.

After trying without much success to change the code a bit to see if I got it to run on PHP 5.2, I decided to leave it alone and work on the code another day. However: (1) I was not willing to uglify my code just to make it run on an old PHP; (2) more generally, I wasn't willing to work with PHP again; and (3) I was having difficulties to sleep that day. Conclusion: I stared writing a Lisp→PHP translator, primarily for the lol. One more badly-slept night later, and so it was that I had a Lisp→PHP translator (or compiler, if you prefer) that did little, but enough to convince me that the idea was at least feasible. Thus lows, or Lisp for Old Web Servers, was born.

Idea

The idea of the project is to create a Lisp-like language which satisfies the following criteria:

Compile to PHP 5.2. The idea is for me to be able to run the resulting code at inf.ufrgs.br (and ideally write the next version of the blog in lows), so I need to target specifically PHP 5.2. I could also try to convince the admins at INF to upgrade the server, but (1) I don't think that's going to happen any time soon, and I was not willing to wait; (2) at this point I had already taken the limitation to PHP 5.2 as a challenge (remember that I started the project to kill time while I couldn't sleep); (3) there is already a similar project, called Pharen, which targets PHP 5.5, and I wanted a distinctive feature (a.k.a. excuse) to justify my project.

Emit relatively straightforward PHP code. As much as possible, the PHP code resulting from compilation should be a more or less direct translation of the lows source. The idea is to ease debugging (and in particular the task of finding the lows code corresponding to a PHP error message), and also the hope that the more direct the resulting code, the smaller the impact on performance of writing the code in lows rather than directly in PHP.

Integrate easily with PHP. It must be possible to use PHP functions, classes, etc. from lows code and vice-versa, without requiring conversions, annotations and the like.

Keep a Lisp-like essence. The idea is not simply to make a redressing of PHP in S-expressions, but rather a language which enables programming in a semi-functional and "Lispy" style and avoids the oddities of PHP as much as possible (while introducing new oddities of its own).

This set of goals influences both the implementation (which must emit relatively clean/direct PHP code) and the design of the language (which must not stray away too much from PHP to allow a relatively direct translation and compatibiltity with PHP code).

Transforming Lisp into PHP

Expressions and statements

A challenge I found right at the beginning is the fact that PHP makes a distinction between expressions and statements, which (mostly) does not exist in Lisp. In particular, things like if, let (local variable declaration) and progn (runs a sequence of expressions and returns the value of the last one, more or less like a block in braces in PHP, but yielding a value) are expressions in lows. if in principle could be translated to the ternary operator (test? then : else), and let could be more-or-less worked around because assignment is an expression in PHP. The problem is that PHP does not have a comma operator like that of C. Things like:

(+ 1 (let ((x 23)
           (y 42))
       (* x y)))

don't have a direct translation to PHP, because it is not possible to write 1 + ($x=23, $y=42, $x*$y). A kludgy solution would be to emit:

1 + ((($x=23) || TRUE) ? ((($y=42) || TRUE) ? ($x*$y)
                                            : whatever)
                       : whatever)

which emulates the comma operator by using only one branch of the ternary operator, but (1) that doesn't work in the general case (in particular, if one of the expressions is a progn containing an echo or some other statement-y thing); (2) that goes totally against the idea of emitting straightforward code. The solution is to move the assignments to before the addition, but, in general, just moving anything that is not an expression to before the expression is not enough: if the branches of a conditional contain expressions with side effects, they cannot be moved out of the conditional. For instance, in something like:

(defun print-and-return (val)
  (echo "The value is " val "\n")
  val)

(+ 1 (if (> x y)
         (let ((a (print-and-return (- x y))))
           (* a a))
       0))

it is not possible to translate the addition to:

$a = print_and_return($x-$y);
1 + (($x>$y)? ($a*$a) : 0)

because print_and_return cannot be called before the test $x>$y is performed.

[At this point, perhaps it ocurred to you (as ocurred to me) the thought: "Okay, why don't we just forbid complex expressions like these nested in other expressions? When will I use that anyway?" But that is exactly the kind of weird limitation which we are trying to escape from by creating a new language instead of programming in PHP! "Do not tell me “that’s what you get for doing weird things”. If two features exist, someday, someone will find a reason to use them together."]

The solution I found was to translate (if ...) to a PHP if block, store the value of the if expression into a temporary variable, and use the temporary in the addition. The previous example becomes something like:

if ($x>$y) {
    $a = print_and_return($x-$y);
    $tmp = $a*$a;
} else {
    $tmp = 0;
}

1 + $tmp

This means that to translate an expression like (+ ...), it may be necessary to emit blocks of code before the translation of the addition itself. As a consequence, the translation function cannot be just something like:

translate[(+ lhs rhs)] = translate[lhs] + translate[rhs]

because both lhs and rhs may require inserting blocks of code before the addition (and the addition itself may be nested in another expression).

The solution I found for this problem was to make the translation functions return two values: the equivalent expression in PHP, and a list of "effects", which are basically (but not necessarily) instructions to emit code in the surroundings of the translation. For example, the translation function, when applied to the example if, yields the expression $tmp (which can be inserted in the middle of another expression which uses the value of the if, and the effect (EmitBefore PHP-if-block), which indicates that PHP-if-block must be inserted before the expression containing the if when emitting the PHP code. Since the insertion can only be performed outside of an expression, the effect is propagated by the functions responsible for translating expressions, until it arrives at a function which emits statements (e.g., the body of a PHP if block, or a function body), where it can then be emitted. Pseudocodefully:

translate[(+ lhs rhs)] =
   let
       lhs-trans; lhs-effects = translate[lhs]
       rhs-trans; rhs-effects = translate[rhs]
   in
       lhs + rhs; lhs-effects ++ rhs-effects


translate-statement[item] =
   let
       item-trans; item-effects = translate[item]
   in
       (code corresponding to the EmitBefores in item-effects) ++ item-trans ;
       (effects in item-effects excluding those EmitBefores already processed)

The same mechanism can be used to emit code in other situations (e.g., in the case of lambda, as we'll see later), or to collect and propagate information during translation. For example, when a variable x which has no visible declaration is used, a (Global x) effect is generated. The function responsible for translating functions collects those effects to generate global $x; declarations at the beginning of the function.

lambda

The next challenge was to translate lambda to PHP. PHP >=5.3 has closures (somewhat crappy ones – one must declare explicitly which variables are to be captured by the closure – but they exist), but PHP 5.2 doesn't. The next thing I thought was to use a "callable" class with an __invoke magic method, but it turns out that callable classes were introduced only in PHP 5.3 too. However, the functions which accept callable things in PHP, such as call_user_func and usort, accept arrays of the form Array(object, method-name) as callables. So, this is something that lambda can return.

Capturing variables in a closure proved much easier than I anticipated, thanks to PHP references. A closure in lows is represented as a class with a member/slot/property/attribute/whatever for each captured variable. When the class is instantiated, the variables are passed by reference to the constructor. Inside the body of lambda, references to captured variables x are translated to $this->x; because $this->x was initialized with a reference to the captured $x, the lambda body sees the same variable $x through the attribute, even reflecting modifications to it.

As an example, something like:

(defun adder (x)
  (lambda (n)
    (+ x n)))

(defun main ()
  (let ((f (adder 10)))
    (call_user_func f 5)))

turns into something like:

class Closure1 {
    function __construct(&$x) {
        // Variable capture.
        $this->x = &$x;
    }

    function __invoke($n) {
        // lambda body.
        return $this->x + $n;
    }
}

function adder($x) {
    // Create the closure, passing the variables to be captured to the constructor,
    // and returns a value that, when called, calls the closures' "__invoke" method.
    return Array(new Closure1($x), "__invoke");
}

function main() {
    $f = adder(10);
    return call_user_func($f, 5);
}

And so, PHP and its references surprise us positively (which is a surprise in itself).

By the way, the effects mechanism is used here for two things: (1) emitting the class before the function containing the lambda is done by propagating an (EmitBeforeToplevel class-definition) effect; (2) each reference to a variable external to the lambda generates a (CapturedVar x) effect; these effects are collected by the function responsible for translating lambda to find out which attributes must be initialized in the class and which arguments must be passed to the constructor. When I came up with the effects idea I hadn't thought about all those applications, so it much gladdened me to find out I could use the mechanism for those things too.

Name clashes

In PHP, local variables have the scope of the entire function where they are created, not just the block where they were declared. As a consequence, in code like:

(let ((x 23))
  (echo "x here is 23: " x "\n")
  (let ((x 42))
    (echo "x here inside is 42: " x "\n"))
  (echo "x out here still is 23: " x "\n"))

we cannot use the same name for both x variables in the translation, because the innermost definition of x would overwrite the outermost one. The solution is to rename one of the (or both) variables. Ideally we should perform the minimum number of renames possible, to make it easier to read and debug the resulting code. However, the current implementation simply renames all variables (adding a _number_ prefix), since testing when a variable must be renamed is not very simple. This decision is non-local: even if there is no visible declaration of a variable x at the point where a (let ((x 23)) ...) occurs, it is still necessary to rename x if at some later point in the function a global variable x is referenced.

The renaming / temporary name generation algorithm assumes that names beginning with _number_ are reserved to the compiler. I think this is not a problem in practice. (In the case of local variables, a variable _42_ would be renamed to something like _1__42_ anyway.) A more serious problem of this approach is at the global scope, in particular in the names of generated classes which implemente closures (e.g., _1_Closure), because those names may conflict with closures created in other files (e.g., when the translation results of multiple files are included into a single PHP program). Perhaps a solution is to include the file name in the name of the class, or to compute a hash from the closure code (but this would still cause conflicts if an identical lambda appears in another file), or something. I don't know. It would also be nice if the class name were descriptive enough to indicate where the definition came from in the source code (e.g., _1_Closure_file_function_line). [Side remark: namespaces don't exist in PHP 5.2.]

A more subtle variable conflict occurs when a let is executed multiple times and a lambda captures a variable defined by the let. For example, supposing the existence of a while construction:

(let ((array-of-lambdas (array))
      (i 0))
  (while (< i 5)
    (let ((n 0))
      (array_push array-of-lambdas
                  (lambda ()
                    (set! n (+ n 1))
                    (echo n))))
    (set! i (+ i 1))))

This would be translated to:

Class _1_Closure {
    function __construct(&$n) {
        $this->n = &$n;
    }

    function __invoke() {
        $this->n = $this->n + 1;
        echo $this->n;
    }
}

$array_of_lambdas = Array();
$i = 0;
while ($i < 5) {
    $n = 0;
    array_push($array_of_lambdas,
               Array(new _1_Closure($n), "__invoke"));
    $i = $i + 1;
}

The problem is that all iterations of the loop use the same $n variable, which is passed by reference to the closure constructor; the correct would be for each iteration to capture a different $n. The solution is to emit an unset($n) at the end of the while body, so that each iteration would create a new variable, but I haven't implemented this yet.

Pretty-printed PHP

One of the goals of the project is to emit readable PHP code, and this involves emitting properly indented code. After some false starts (in the initial version, the translation functions emitted PHP code strings directly, and my original idea was to use some special ASCII characters to indicate "increase indentation" and "decrease indentation" when printing, but I realized that I could not choose any characters for that because any character can appear in a string; moreover, mixing code generation and formatting questions was becoming rather ugly), I decided to make the translation function emit structures representing PHP abastract syntax trees (ASTs). After translation, the trees are passed to a print-php function, which takes care of the gory details of printing the code with line breaks, indentation, spaces and parentheses at the proper places. Separation of concerns FTW.

The future

As this post turned quite long, and I should get some sleep, we'll finish here. In a future post, I intend to talk about some features that are still missing, such as classes, method calls and other stuff, as well as trickier design decisions (which were the initial goal of this post, but anyway). If you are interested, you can look at the code on GitHub.

1 comentário / comment

Criando apresentações de slides em LaTeX/Beamer usando o Org mode

2015-08-19 00:27 -0300. Tags: comp, editor, emacs, latex, em-portugues

Já pensou em poder escrever seus slides assim:

* Recovering memory safety

- How can we recover memory safety in C programs?
- Traditional solution: add metadata to allow checking
- This has a number of drawbacks:
  - It *changes memory representation* of objects
    - requires recompilation of everything (external libraries, OS syscalls)
  - C pointers can point to any part of an object
    - No simple/cheap way to find metadata from an arbitrary pointer
    - Pointers themselves must carry bounds, \\
      or separate data structure must be looked up
    - Changes representation and/or is expensive
- But there is another way...

E eles ficarem assim?

[Slide produzido pelo backend de exportação para Beamer do Org mode]

Pois isso é possível usando o backend de exportação do Org mode do Emacs para Beamer, um pacote LaTeX para criação de slides.

Ingredientes

Você vai precisar de:

Org mode

Explicar o que é o Org mode é um tanto quanto complicado, porque ele faz uma porção de coisas. Segundo o site do projeto, "Org mode is for keeping notes, maintaining TODO lists, planning projects, and authoring documents with a fast and effective plain-text system". Se você nunca usou o Emacs, um modo basicamente define um conjunto de funções, keybindings e comportamentos para edição de um certo tipo de arquivo. Por exemplo, existe um c-mode, html-mode, etc. O Org mode é bem mais mágico que isso, mas para o objetivo deste post, o Org mode é basicamente um modo para editar arquivos em um formato de plain-text estruturado (a la Markdown). O Org mode permite exportar esses documentos para diversos formatos usando uma variedade de backends de exportação, entre eles o beamer.

O backend beamer não é carregado por padrão. Você pode configurar quais backends são carregados por padrão executando M-x customize-variable RET org-export-backends RET (i.e., tecle Meta+X (i.e., Alt+X), digite customize-variable (usando TAB para completar, se desejar), dê Enter, digite org-export-backends, e dê Enter novamente). Selecione a checkbox beamer e clique em "Apply and save". Tecle q para sair da janela de Customize.

Para criar um documento, crie um arquivo com a extensão .org (e.g., tecle C-x C-f exemplo.org RET (i.e., tecle Ctrl+X, Ctrl+F, digite o nome do arquivo e dê Enter)). O Emacs deverá entrar no modo Org automaticamente.

Para ter uma idéia do markup utilizado, dê uma olhada no arquivo de exemplo linkado no começo do post. Basicamente:

Exportando o arquivo

Para exportar, tecle C-c C-e. Isso abrirá o "Org Export Dispatcher", um painel com dúzias de opções de exportação. Tecle l ("Export to LaTeX"), e depois P ("As a PDF file (Beamer)"). Alternativamente, você pode selecionar B ao invés de P, o que coloca a saída em LaTeX em um novo buffer, ao invés de gerar o PDF diretamente. Se ocorrer algum erro durante a exportação, você pode ver a saída do pdflatex no buffer *Org PDF LaTeX Output* (você pode ir para o buffer usando o menu "Buffers" na interface gráfics, ou teclando C-x b e digitando o nome do buffer (usando TAB para completar se desejado)).

Resumindo: C-c C-e l P (i.e., Ctrl+C, Ctrl+E, letra L, Shift+P).

Miscelânea

Por padrão, quando um arquivo Org é aberto, ele é mostrado com as seções colapsadas. Use Shift+TAB para expandir (ou re-colapsar) todos os títulos, ou TAB sobre um título para expandir aquele título específico.

Mais informações

Eu sou novo tanto no uso do Org mode quanto do Beamer, então não tenho muito mais o que dizer no momento. Mais informações podem ser encontradas no manual do Org mode dentro do próprio Emacs (C-h i, procure por Org mode) e no site do Org mode.

Customizações na aparência da apresentação são realizadas primariamente incluindo os comandos LaTeX apropriados usando linhas #+LATEX_HEADER. Para mais informações, você pode procurar diretamente por informação sobre o Beamer, ao invés de especificamente sobre o Org Mode.

Se você nunca usou o Emacs, pode querer dar uma olhada no How to Learn Emacs: A Hand-drawn One-pager for Beginners.

3 comentários / comments

Mail, mail, mail

2015-08-10 23:13 -0300. Tags: comp, rant, life, em-portugues

Como é o causo de quando em quando por aqui (e provavelmente mais quando do que se me apercebe), este post tem o objetivo único e exclusivo de reclamar da vida (com uma e outra informação útil solta, talvez, e meramente incidental). Com este anteaviso, prossigo o relato.

Até agora, o meu cliente de e-mail favorito era o Claws Mail. Agora parando para pensar, faz pelo menos uns 10 anos que eu uso o Claws Mail (quando eu comecei a usá-lo, ele ainda se chamava Sylpheed-Claws), e durante a maior parte desse tempo, eu fui um cliente satisfeito.

Eu sempre preferi manter cópia dos e-mails localmente, então eu baixava os e-mails via POP. (Além disso, no 3G era simplesmente inviável usar IMAP.) Em algum momento no ano passado, o INF resolveu defasar o POP em favor do IMAP. Durante um certo período, o POP simplesmente ficou fora do ar (desconfio que isso não tenha sido intencional). Além disso, o sistema anti-spam, que anteriormente marcava as mensagens como spam adicionando [***SPAM***] ao subject, passou a mover as mensagens para uma pasta Junk, que não vem pelo POP. Como é da natureza dos anti-spams marcar coisas como e-mails de confirmação de inscrição como spam, ficar sem ver essa pasta não era factível. Conclusão: tive que migrar para o IMAP.

Turned out, entretanto, que o suporte ao Claws a manter as pastas de IMAP sempre sincronizadas para uso offline era menos que excelente (e acho que nessa época eu ainda usava 3G), então usar o IMAP da INF diretamente não era viável. Além disso, nesse meio tempo eu comecei a ler e-mail pelo celular, e ter as contas não-sincronizadas no celular e no PC estava meio desagradável. A solução que eu encontrei então foi usar um programinha chamado OfflineIMAP, que permite manter uma mailbox local sincronizada com um servidor IMAP. Diferentemente dos fetchmails da vida, a sincronização do OfflineIMAP é bidirecional: mudanças em qualquer uma das pontas são propagadas para a outra. O problema é que o OfflineIMAP só suporta mailboxes no formato Maildir, e o Claws só suporta mailboxes no formato MH. A solução comumente adotada nessa situação é instalar um servidor IMAP local (eu usei o Dovecot), fazer o OfflineIMAP sincronizar o servidor IMAP remoto com o servidor IMAP local (ao invés de uma pasta Maildir), e apontar o cliente de e-mail para o IMAP local. Deu um trabalhinho, mas consegui.

Um inconveniente disso é que se você quiser atualizar os e-mails manualmente ao invés de esperar o próximo momento de atualização, é necessário mandar um sinal para o processo (pkill -USR1 offlineimap). Outro inconveniente é que o OfflineIMAP não consegue identificar que uma mensagem foi movida para outra pasta; na sincronização, ele deleta a mensagem original na outra ponta e faz upload da mensagem para a pasta nova. A primeira conseqüência disso é que não é possível mover coisas para a pasta Junk localmente, porque o IMAP da INF não aceita fazer uploads de mensagens novas para a pasta Junk, apenas que mensagens de outras pastas sejam movidas para ela. A outra conseqüência é que e-mails grandes movidos localmente precisam ser uploadeados novamente, o que é lento e sujeito ao famoso trancaço da INF, uma doença que afeta todos os serviços da INF (IMAP, POP, HTTP, SSH, you name it) desde absolutamente sempre, em que o servidor da INF esquece da conexão e o upload/download simplesmente trava. Baixar os e-mails por POP via 3G era algo que quase sempre exigia múltiplas tentativas (e como POP não suporta "resumar" o download de uma mensagem, os e-mails de 5MB da lista da graduação com PDFs e fotos de produtos à venda eram algo especialmente lamentável, em particular porque normalmente eu não tinha o menor interesse nos e-mails de 5MB em questão). Bons tempos. Mas o problema ainda persiste, em escala menor, com os uploads desnecessários de mensagens de 5MB do OfflineIMAP. Mas ok, no geral, tudo funciona mais ou menos bem.

O problema é que agora o Claws Mail não estava mais notificando a chegada de mensagens novas. As it happens, a idéia do Claws de o que é uma mensagem nova em uma conta IMAP depende da flag "Recent" mantida pelo servidor; essa flag diz se uma mensagem já foi de alguma forma vista por um cliente. Se uma mensagem não possui essa flag, o Claws não a considera como uma mensagem nova, mesmo que ela esteja não-lida. De certa forma faz sentido, porque se você já abriu a mailbox em um outro aparelho e viu que a mensagem estava lá (mesmo sem tê-la aberto), você já sabe que ela existe e não precisa ser notificado. O problema é que a flag Recent é de inteiro controle do servidor IMAP – ela é removida automaticamente quando uam mensagem é "vista" e um cliente IMAP simplesmente não tem como readicionar a flag –, e o OfflineIMAP, no processo de baixar as mensagens para o servidor local, "vê" a mensagem. Well, fuck.

Na verdade o problema no Claws é pior, porque o Claws não aplica filtros automaticamente a mensagens que não sejam novas. Ok, dá para fazer a filtragem diretamente no servidor, configurando os filtros pelo webmail do serviço. O problema é quando o filtro deveria mover a mensagem para fora da mailbox. Por exemplo, eu tinha uma regra que movia todas as mensagens da CONLANG Mailing List para uma mailbox local (onde eu mantenho todo o archive da lista). Além de não aplicar os filtros a mensagens vistas, o Claws também não aplica filtros a mensagens novas que não estejam na Inbox da mailbox. Isso quer dizer que se eu adiciono um filtro no webmail para mover as mensagens da CONLANG para uma pasta "Conlang", e um filtro local para mover as mensagens novas em "Conlang" para o archive local, o filtro local não funciona.

Eu fiquei convivendo com essa situação assim mesmo por um bom tempo. Há uns meses atrás, eu me irritei com o Claws e fui ver se achava um outro cliente de e-mail que me agradasse. O primeiro que eu tentei foi o Thunderbird, que na ocasião eu não gostei não lembro mais por que razões específicas. No processo de procurar um cliente de e-mail novo eu acabei lembrando do Gnus, que foi o motivo original pelo qual eu acabei começando a usar o Emacs. No fim, eu resolvi parcialmente o meu problema com o Claws comentando fora o teste que limitava os filtros apenas à Inbox, larguei de mão o Gnus, e tudo ficou bem (com o efeito colateral de que eu comecei a usar o Emacs, so: yay). It soon turned out, entretanto, que a versão do RSSyl (o plugin leitor de feeds do Claws) que veio na versão que eu recompilei do Claws estava falhando em atualizar alguns feeds (na verdade ele parecia só estar perdendo um feed específico, o Little Strange World, mas eu não sabia quantos mais ele podia estar perdendo sem eu saber), então eu acabei voltando a usar o Claws pré-compilado do Debian e convivendo com seus defeitos. Era desagradável não ter barulhinho de notificação e ter que mover as mensagens da CONLANG para o archive local manualmente (eu acabei parando de mover), mas estava dando para engolir.

A última gota foi na última sexta-feira. Eu recebi um e-mail no começo da tarde com o subject "Matricula 2015/2 - URGENTE", me informando que eu não havia feito a matrícula nesse semestre e me pedindo para "nos informar uma posição a respeito". Isso me deixou bastante alarmado; eu achei que não era necessário me reinscrever na Dissertação de Mestrado, pois no Trabalho de Graduação na INF o camarada só se inscreve no semestre inicial e não é necessário fazer nada no semestre seguinte para continuar matriculado. Respondi explicando a situação e perguntando o que deveria fazer. Algum tempo depois, responderam-me da secretaria informando que eu deveria realizar a matrícula. A essa altura, entretanto, o período de matrícula já tinha encerrado há uma semana, então não era possível fazer isso pelo Portal do Aluno. Às 17h08 mandei um e-mail perguntando como eu deveria fazer a matrícula, e fui trabalhar no bendito código da minha dissertação. Turns out que às 17h09 tinha vindo uma resposta, perguntando em que disciplinas eu gostaria de me matricular, mas eu só vi isso por volta das 17h45, pois o Claws não notificou. A secretaria fecha às 17h30, o que significa que um problema que eu poderia ter resolvido no mesmo dia só seria resolvido na segunda (at best) por conta da tosquice do meu cliente de e-mail. Just kill me.

Eu passei o resto da sexta-feira tentando resolver o problema dos e-mails novos, e foi então que eu descobri o causo da flag Recent e o fato de que, como ela é controlada pelo servidor, não tem muito o que fazer (talvez haja alguma gambiarra, mas não encontrei). Resolvi então tentar usar o Claws direto com o IMAP da INF e evitar o OfflineIMAP, já que agora eu não uso mais o 3G. O que aconteceu foi algo ainda mais bizarro: algumas mensagens estavam sendo remarcadas como novas depois de já terem sido vistas (mas não lidas). Além disso, as mensagens que estavam sendo filtradas no servidor não estavam sendo marcadas como novas (não sei se porque o Claws considerou que mensagens novas fora da Inbox não eram dignas de serem chamadas de novas, ou se porque o sistema de filtragem no servidor acaba marcando as mensagens como "vistas" como efeito colateral, ou se por algum outro motivo). A essa altura eu enchi o saco do Claws e fui catar um cliente de e-mail novo.

(No processo de tentar figure out os problemas eu descobri uma porção de coisas legais, entretanto; a principal delas é que você pode usar o comando:

openssl s_client -crlf -connect servidor:porta

como uma espécie de netcat para abrir uma conexão SSL, e dar comandos de IMAP via terminal. Por sinal, eu achei bem mais conveniente fazer isso de dentro do Shell mode do Emacs do que por um emulador de terminal. But I digress.)

Thunderbird

Eu resolvi dar uma segunda chance para o Thunderbird/Icedove. And boy is it terrible. Para começar, o Thunderbird não faz a distinção entre mensagens novas e não-lidas que o Claws (quando funciona) faz; não existe um comando para saltar para a próxima mensagem nova, apenas para a próxima não-lida, o que é um problema porque eu tenho o costume de deixar mensagens que requerem atenção futura como não-lidas e não quero desmarcá-las sem querer enquanto leio mensagens novas. By the way, o atalho para ir para a próxima não-lida (N) não funciona quando você não está com uma pasta aberta (se você está na paginazinha da conta, por exemplo).

"Ah, mas você pode marcar as mensagens como 'starred' ao invés de marcar como não-lidas", right? A diferença é que uma pasta com uma mensagem não-lida aparece em negrito e com o número de mensagens não lidas ao lado do nome, o que me chama atenção para o fato de que há alguma pendência ali. Uma pasta com mensagens starred não mostra nenhuma indicação de que contém mensagens starred; o Claws pelo menos mostra um "✓" nos ícones das pastas que contêm mensagens marcadas, o que é bem pouco perceptível no meu tema com fundo preto, mas é melhor que nada. Por falar em tema, diversos elementos da interface do Thunderbird ignoram o meu tema e mostram elementos com cores incompatíveis, seja meramente destoantes do restante, seja com o texto da cor certa e o fundo da cor errada (e.g., branco no fundo cinza). Tudo bem que o meu tema é um arquivo gtkrc hacked together de qualquer jeito cinco anos atrás que eu basicamente nunca mais mexi, mas a maioria dos programas funciona relativamente bem com ele. Anyway.

O programa simplesmente não foi pensado para ser usado com o teclado. Coisas como alternar entre headers completos e simples não possuem um atalho de teclado; para adicionar um atalho, é necessário instalar um addon (!). O atalho do "quick filter" (o equivalente do search do Claws) é Ctrl+Shift+K; no Claws, é /. Aliás, o termo de pesquisa se perde quando se troca de pasta, e não tem histórico; no Claws o termo fica até a caixa ser fechada (com Esc, por exemplo), e ↑/↓ percorrem os últimos termos usados. Navegar pela lista de mensagens com as setas automaticamente seleciona a mensagem para visualização (o que a marca como lida, obviamente); no Claws, isso é configurável. O Claws permite habilitar o comportamento que era o default em aplicativos GTK 1, em que se pode parar sobre um comando de um menu qualquer e pressionar uma tecla para associá-la como atalho para aquele comando; assim, a maioria dos keybindings pode ser facilmente customizada. Alguns elementos da interface do Claws parecem ter sido projetados especificamente para funcionar bem com isso; por exemplo, o menu "Edit > Advanced" da janela de composição de mensagens contém entradas que parecem não fazer muito sentido em um menu, como "Move to beginning of line" e "Delete a word backward", até que você se dá conta de que eles estão ali para que você possa associar as teclas de atalho de sua preferência a esses comandos. Esse é o tipo de atenção a detalhes que torna o Claws totalmente excelente. Eu na verdade fiquei meio chocado com o fato de que um programa da idade do Thunderbird tenha tantos gaps de funcionalidade/usabilidade. Talvez o usuário no público-alvo do Thunderbird tenha expectativas diferentes da interface. I don't know.

On the bright side, o Thunderbird tem um search global por todas as pastas, que o Claws não tem. Além disso, a sincronização do IMAP para uso offline funciona direito. Finalmente, o Enigmail (add-on do Thunderbird para uso de PGP) parece ser fácil de usar. Acho que isso resume o que eu tenho de bom para dizer sobre o Thunderbird.

Ah, right, o problema das notificações: por algum motivo, o Thunderbird também não está mostrando notificações quando chegam mensagens novas (o que me dá alguma esperança de que o problema seja elsewhere e eu consiga corrigi-lo e voltar a usar o Claws), mas pelo menos eu achei um addon que permite adicionar um ícone à systray com o número de mensagens não-lidas em pastas selecionadas, o que resolve parcialmente meu problema. (O Claws também tem um plugin para adicionar um ícone à systray, mas ao invés de mostrar um número ele mostra um ícone não-customizável e horrível de perceber se está indicando a presença de mensagens não-lidas ou não.) A outra "vantagem" do Thunderbird é que eu achei ele tão ruim de usar que eu provavelmente vou continuar procurando outra solução ao invés de me acomodar com os problemas como eu fiz com o Claws por um ano e tanto.

Gnus

And then there is Gnus. Como dito anteriormente, o Gnus foi o que me levou a mexer no Emacs de novo, mas eu acabei largando ele de mão, em parte porque eu achei que tinha resolvido meu problema com o Claws, em parte porque configurar o Gnus é um tanto quanto não-trivial (e aprender a usá-lo também), especialmente para mim que não tinha praticamente nenhuma experiência com o Emacs então. Agora que eu já estou há mais de dois meses usando o Emacs (estou-vos devendo um post sobre isso, por sinal) e já estou relativamente habituado à criatura, acho que está na hora de dar uma segunda chance ao Gnus.

O Gnus é bastante bizarro, na verdade. Ele faz uma porção de coisas que fazem sentido em um news-reader, mas not so much em um mail-reader. Por exemplo, por padrão ele oculta "grupos" (pastas) que não têm mensagens não-lidas, e oculta as mensagens lidas em um grupo. Eu passei o último sábado quebrando a cabeça tentando configurar o dito cujo, e turns out que o problema que eu passei mais tempo tentando resolver era um bug no Gnus (que aparentemente eu vou ter que reportar uma hora, pois não achei nenhuma menção dele na Internet). Fazer ele funcionar com duas contas de e-mail, e escolher a conta apropriada para mandar mensagens dependendo do contexto, salvar a mensagem enviada na pasta apropriada, etc., é algo que eu tenho que olhar com calma como se faz. Pelo menos o conceito do Gnus de uma mensagem vista aparentemente é independente de o que o IMAP acha que foi visto, o que já é um bom começo. Apesar de ser complicado e cheio de bizarrices, eu tenho a sensação de que o Gnus é suficientemente hackable/customizável para eu conseguir fazê-lo fazer o que eu quero (de certa forma isso é meio verdade sobre o Emacs como um todo). Se eu for bem-sucedido, provavelmente vou postar um tutorial de configuração por aqui.

EOF

Well, esse post ficou bem maior do que eu esperava. Mas agora que eu já escrevi, vou publicar e eras isso. Já que você chegou até aqui, leve umas músicas como recompensa.

3 comentários / comments

FISL 16

2015-07-11 23:27 -0300. Tags: comp, life, freedom, em-portugues

Nos últimos quatro dias ocorreu o 16º Fórum Internacional do Software Livre. Minha proposta de palestra sobre o lash não foi aceita (o que por um lado foi bom, porque o projeto anda meio dormente devido a obrigações mestrariosas e má administração temporal), mas eu assisti uma porção de palestras, a maioria das quais foram bem boas. Também encontrei o Marcus Aurelius por lá, que trabalhou como voluntário na tradução do site do FISL para o esperanto, e troquei umas palavras com seu Lucas Zawacki, que estava no espaço do Dumont Hackerspace (e que ganhou a Hackathon EBC/FISL 16 com o aplicativo Pautaí, por sinal; congrats!).

Eis um resumo das palestras que eu assisti por lá. O site do FISL contém a programação completa, com link para os vídeos da maioria das palestras.

Dia 1

Email criptografado: usando GPG e icedove para criptografar sua correspondência, por Felipe Cabral

A idéia era que fosse uma oficina, mas acabou sendo mais uma palestra sobre conceitos básicos de PGP. Também foi visto um programinha chamado Seahorse (pacote seahorse no Debian) para gerar e administrar chaves de PGP e afins. Eu fui na palestra achando que ia aprender alguma coisa que me ajudasse a fazer o PGP funcionar no Claws Mail. Turns out que no Thunderbird/Icedove, tudo o que é necessário para fazer o PGP funcionar é instalar o add-on Enigmail (pacote enigmail no Debian), seguir os passos de configuração que serão apresentados depois que o Enigmail é instalado, e ser feliz. Me deu até vontade de experimentar o Thunderbird de novo, mas tem que ver isso aí. Meanwhile, continuo unenlightened quanto ao Claws.

Half my life with Perl, por Randal Schwartz

O autor do Programming Perl (a.k.a. Camel Book), entre outros, e atual host do podcast FLOSS Weekly, conta sua vida e seu envolvimento com o Perl. Foi uma palestra bem interessante, mesmo eu não conhecendo grandes coisas do Perl e sua comunidade.

Negócios em Software Livre, isso existe?, por Vagner Fonseca

Uma palestra sobre como ganhar dinheiro vendendo serviços baseados em software livre para empresas, tais como suporte, customização de programas livres de acordo com as necessidades da empresa, soluções de monitoramento de rede e de uso de recursos, entre outros. Também foram discutidos tipos de contrato (por projeto, por hora, contrato de suporte) e quando cada tipo vale a pena (basicamente, cobrar por projeto só vale a pena para coisas bem simples e punctuais, caso contrário corre-se o risco de algo demorar muito mais do que o previsto e o cidadão acabar efetivamente pagando para trabalhar; em contrato de suporte se cobra menos do que em um contrato por hora, mas tem-se a estabilidade e se consome um tempo mais limitado por semana em uma empresa, então é possível atender mais de uma ao mesmo tempo), entre outras coisas.

"Enemy spotted - Applying infovis at security field", por Felipe Afonso Espósito

Uma palestra sobre visualização de dados e como isso pode ser usado com dados de segurança. Não tenho muito mais o que comentar.

Let's Encrypt: Uma Autoridade Certificadora Gratuita e Automatizada, por Seth Schoen

Seth Schoen, membro da EFF, falou (em português!) sobre o andamento do Let's Encrypt, uma iniciativa da EFF, Mozilla, University of Michigan e outros para criar uma autoridade certificadora e um mecanismo para obtenção e validação automática e gratuita de certificados digitais, e instalação automática em servidores web, eliminando uma barreira que existe atualmente para se usar HTTPS. A data prevista para o serviço ser disponibilizado para o público é 14 de setembro deste ano.

Eu pretendia assistir a APIs em Tempo Real Usando Websockets em PHP, mas a sala lotou e eu não cheguei a tempo. Ao invés disso, eu e o Marcus ficamos tentando entender as entranhas do cua-mode do Emacs, inter alia.

Dia 2

Encontro de Hackers GNU

Alexandre Oliva (FSF Latin America), Felipe Sanches, e Deborah Anne Nicholson (Open Invention Network, MediaGoblin) falaram sobre uma porção de projetos relacionados com liberdade digital. Entre eles, o Twister, uma plataforma peer-to-peer de microblogging, e MediaGoblin, uma plataforma descentralizada de compartilhamento de mídia. Também foram mencionadas algumas idéias de projetos que seriam interessantes de criar, como uma plataforma peer-to-peer para distribuição de código (eu mencionei que já existe um projeto nesse sentido).

Outra idéia interessante que o Alexandre Oliva mencionou é que os ambientes computacionais modernos são pouco programáveis (sounds weirdly familiar), e que seria interessante criar uma biblioteca que facilitasse para o usuário descobrir que funções o programa chama quando se clica em algum botão ou menu e tornasse esses programas programáveis. O conceito é parecido com o que eu vejo como ideal de ambiente computacional, mas eu nunca tinha pensando em pôr essa funcionalidade em uma biblioteca, ao invés de algo mais fundamental. É uma abordagem interessante de se pensar.

O Felipe Sanches também comentou a questão de firmware aberto, as dificuldades envolvidas em reverse-engineering de firmware e hardware, e a necessidade de incentivar o compartilhamento de informação sobre técnicas de engenharia reversa.

Programação Orientada a Objetos em C puro: o caso do htop, por Hisham Muhammad

O autor do htop falou sobre as técnicas de programação que usou no desenvolvimento desse programa, em particular o uso de orientação a objetos em C. Eu já sabia um pouco sobre o assunto, mas a palestra valeu a pena mesmo assim. Mais para o final, o Hisham falou sobre o uso de collections para gerenciar "ownership" de ponteiros (alocação e liberação de memória) em C, e como isso torna a gerência manual de memória do C menos horrível de se usar. Por fim, ele falou sobre o dit, um editor de texto que ele escreveu reusando componentes do htop.

Assinatura digital e o padrão ICP-Brasil, por Paulo Cesar Barbosa Fernandes

Uma palestra sobre o padrão brasileiro de assinaturas digitais, seus aspectos legais, e algumas informações gerais sobre o uso de assinaturas digitais. Talvez a coisa mais importante que eu aprendi na palestra é que uma assinatura digital obtida adequadamente seguindo o padrão ICP-Brasil tem o valor legal de uma assinatura de papel (embora ainda haja legislação a ser atualizada para levar isso em conta).

Desvendando o IPv6: Tecnologia indispensável para o Futuro da Internet, por Lucenildo Lins de Aquino Júnior

Nada muito aprofundado tecnicamente, mas foi uma boa palestra. O mais surprising aqui foi saber que uma boa parte do tráfego da Internet já é em IPv6, inclusive no Brasil (pelo menos em São Paulo), mas não lembro mais os números. Até me deu alguma esperança de que teremos o bendito IPv6 em alguns anos.

What's new in systemd in 2015, and what's coming in 2016, por Lennart Poettering

Exatamente o que o título diz. Foram mencionadas funcionalidades de configuração de rede, resolução de DNS, containers, e todas essas coisas que nós nos perguntamos se deviam mesmo fazer parte do systemd, mas isso fica para outra discussão.

Finding a Great Project to Work On, or Great People to Work on Your Project, por Deborah Anne Nicholson

Uma palestra muito boa sobre, entre outras coisas, como fazer com que um projeto seja "welcoming" a novos colaboradores, especialmente não-desenvolvedores (pessoas envolvidas com tradução, documentação, divulgação, arte, etc.). Alguns dos tópicos mencionados são coisas que deveriam ser senso comum, mas infelizmente não são, tais como ter uma descrição de o que é o projeto na página inicial, links para mailing lists, FAQs, informação de contato, etc. Outro ponto mencionado é que se você como desenvolvedor não tem grandes skills comunicativos, encontre uma pessoa para fazer esse papel no seu projeto. Also, incentive e recompense os esforços dos colaboradores. Provavelmente é melhor assistir o vídeo do que eu tentar explicar (talvez eu tenha skills comunicativos sub-ótimos).

Acho que esse foi o melhor dia do FISL para mim.

Dia 3

Ocorreu uma miniDebConf (conferência sobre o Debian) durante esse dia na sala 41D.

Debian: o que é, e como funciona, por Antonio Terceiro

Uma palestra introdutória sobre o Debian, que no entanto me ensinou sobre uma porção de recursos online do Debian que eu não conhecia, tais como o blog Bits from Debian, o Ultimate Debian Database (que é mais útil para desenvolvedores do Debian), um pastebin, a página com informações para se tornar um novo membro do Debian, páginas onde se pode navegar pelos fontes do Debian e fazer buscas textuais no código, e um security bug tracker.

Não sou programador, como posso ajudar o Projeto Debian?, por Luiz Guaraldo

Não lembro mais o que foi visto nessa.

Containers and systemd, por Lennart Poettering

Uma palestra sobre o suporte a containers do systemd. A palestra começou com "everyone knows what containers are", except I didn't, então embora as funcionalidades apresentadas tenham parecido interessantes, eu meio que fiquei boiando sobre como as coisas funcionam.

Empacotamento de software no Debian, por João Eriberto Mota Filho

Uma palestra muito boa sobre o processo de criar um pacote Debian, cobrindo as ferramentas utilizadas, o ambiente de empacotamento, os arquivos que se deve editar, etc. Se você pretende criar um .deb na vida, assista.

Dependências de pacotes de código fonte, por Thadeu Lima de Souza Cascardo

Essa palestra pode ser resumida a "pacotes de fonte possuem dependências". Foram vistos alguns conceitos como dependências de compilação vs. dependências de execução, mas nada do que eu esperava ver foi visto, tal como como baixar pacotes fonte e suas dependências e compilá-los usando as ferramentas do Debian. A palestra terminou com o palestrante debugando um script em Perl que ele escreveu para calcular as dependências recursivas de um pacote fonte, e eu fui embora fazer outras coisas pelo mundo.

Dia 4

Decidi almoçar em casa e só consegui chegar às 13h e pouco no FISL.

Javascript e as novidades nas funções em ES2015+, por Felipe Nascimento de Moura

Cheguei uns 10 minutos atrasado na palestra. Ficou bem pouco claro para mim o que eram coisas novas do JavaScript vs. coisas que já existem. Foram vistas features como generators (que o Firefox já suporta há mais de oito mil anos, mas agora eles vão entrar no EcmaScript oficial, com uma sintaxe levemente diferente), execução assíncrona, setInterval para executar ações periodicamente, "arrow functions" (uma sintaxe nova para funções anônimas, e que captura o valor de this), e outras coisas que ya no recuerdo más.

HTTP: passado, presente e futuro, por Luiz Fernando Rodrigues

Aprendi o suficiente para querer me informar melhor sobre o HTTP/2, but that's it.

Programming Efficiently, por Jon "Maddog" Hall

A palestra foi mais sobre a importância da eficiência em programação, e não sobre técnicas de programação eficiente. O Maddog também falou sobre o fato de que muita gente sai de uma escola/faculdade sem saber como funciona o hardware, que o Raspberry Pi foi criado para ser uma máquina "hackable" e boa para o aprendizado, e que ele usou alguns Banana Pi para montar um cluster pequeno, barato e com bom poder computacional que pode ser usado para ensinar high-performance computing e afins. A palestra foi bem boa, embora não fosse sobre o que eu pensei.

How much of your computer is non-free, and how worried should you be?, por Matthew Garrett

Palestra sobre o fato de que nossos dispositivos contêm bem mais processadores do que normalmente a gente pensa, normalmente rodando software proprietário que não temos nem como ver nem como modificar. Exemplos particularmente alarmantes são HDs (com uma menção de uma galera que conseguiu fazer um HD bootar um kernel Linux) e SD cards. Worse still, muitos desses dispositivos só aceitam firmwares assinados pelo fabricante, usualmente encriptados, o que nos impede de analisar e controlar o comportamento desses dispositivos. Recomendo assistir o vídeo.

Archlinux: Você no comando, por Israel Lopes dos Santos

Uma palestra introdutória sobre o Arch Linux, explicando a filosofia da distribuição, o gerenciador de pacotes, o fato de que criar um pacote para o Arch é relativamente fácil e que qualquer um pode submeter um pacote para o Arch User Repository (AUR). Na seção de perguntas, um cidadão resolveu criticar o uso de "Linux" ao invés de "GNU/Linux" de uma maneira indireta/irônica que o palestrante não entendeu. Foi bem desnecessário; ser indireto e irônico não ganha ninguém à causa, na minha humilde opinião.

That was it. 9/10 would go again.

3 comentários / comments

Como usar o modelo LaTeX do INF/UFRGS para TCCs e afins

2015-06-26 01:16 -0300. Tags: comp, latex, academia, em-portugues

Salve! Como muita gente cai neste blog procurando pelos modelos LaTeX do INF/UFRGS, e eu parei de disponibilizar o meu "pack" em favor do repositório oficial, resolvi escrever um tutorial de como usar o modelo.

Instalando o LaTeX

GNU/Linux

Nas distribuições Debian, Ubuntu e afins, você pode instalar os pacotes do LaTeX com o comando:

sudo apt-get install texlive-latex-base texlive-lang-portuguese

Se você já possui o comando pdflatex, você já tem o LaTeX instalado e pode dispensar esse passo. Porém, é possível que você não tenha o pacote texlive-lang-portuguese, o que pode causar problemas como hifenação incorreta e strings na língua errada na capa do modelo e outras partes com texto pré-definido. Se isso acontecer, verifique se o pacote está instalado. Mesmo que o seu trabalho seja em inglês, o modelo pode não funcionar corretamente na ausência desse pacote.

Outros sistemas

É possível usar o LaTeX em outros sistemas operacionais, como o Windows, mas nunca fiz isso e não sei como é a experiência. Se alguém tiver alguma dica, deixe nos comentários.

ShareLaTeX, Overleaf

Uma outra opção é usar o ShareLaTeX.com ou o Overleaf, que são uma espécie de Google Docs para documentos LaTeX. A vantagem é que você não precisa instalar nada, e pode acessar de qualquer lugar. A desvantagem é que se o servidor do ShareLaTeX.com/Overleaf sair do ar você está ralado, então eu recomendo baixar regularmente o .zip com os arquivos para não correr esse risco. No caso do Overleaf, também é possível sincronizar o projeto com um repositório Git local.

Instalando o iiufrgs

O pacote iiufrgs, que contém os modelos do INF/UFRGS, fica disponível em um repositório do GitHub. Para baixá-lo, você pode clicar em Download ZIP na barra lateral da página, ou usar o comando git para clonar o repositório:

git clone https://github.com/schnorr/iiufrgs.git

A vantagem de usar o git é que você pode usar o comando git pull dentro do diretório do repositório para atualizá-lo.

Dentro do repositório, há um diretório inputs, que contém o pacote propriamente dito, e um diretório examples, que contém alguns documentos de exemplo.

Para usar o modelo, é necessário que os arquivos em inputs estejam no path do LaTeX. Há diversas maneiras de fazer isso:

That's it.

Preparando o documento

O diretório exemplos contém uma porção de (adivinhe só?) arquivos de exemplo para os diversos tipos de documentos:

Copie o arquivo apropriado para o diretório onde você vai colocar seu projeto LaTeX (que pode ser um diretório vazio qualquer que você criou, ou uma cópia do inputs caso tenha optado por não instalar o iiufrgs no local padrão, ou faça upload do arquivo de exemplo para o seu projeto no ShareLaTeX/Overleaf). Renomeie o arquivo se for do seu interesse.

Agora é necessário editar algumas partes do arquivo.

A linha \documentclass indica o tipo de documento a ser gerado. Ela tem a seguinte cara:

\documentclass[cic,tc]{iiufrgs}

Como você já copiou um documento de exemplo do tipo apropriado, a princípio você não tem que mexer nessa linha. Se seu documento for em inglês, adicione a opção english na linha:

\documentclass[cic,tc,english]{iiufrgs}

Há outras opções, que estão documentadas nos comentários do arquivo.

Como mencionado, não há um exemplo de Plano de Estudos e Pesquisa; para isso, use o arquivo ppgc-diss.tex e altere a opção diss para pep na linha \documentclass. Você provavelmente também vai querer apagar coisas como dedicatória e agradecimentos do documento.

As próximas coisas a alterar são a linha \title, que contém o título do trabalho, e \author, que contém o nome do autor. Essa linha tem o formato:

\author{último nome}{primeiro nome e nomes do meio}

A linha \advisor, que contém o nome do orientador, segue o mesmo formato, mas inclui também o título (Prof. Dr., por exemplo). Se houver um co-orientador, descomente a linha \coadvisor e preencha da mesma maneira.

Descomente a linha \location e preencha com a cidade e UF adequados (i.e.,

\location{Porto Alegre}{RS}

que por alguma razão não é o default nos arquivos de exemplo; go figure).

O documento possui uma série de linhas comentadas definindo comandos \nominataWhatever. Na segunda página do PDF gerado a partir do modelo, fica um quadrinho com os nomes dos responsáveis por diversos setores da universidade (reitor, bibliotecário-chefe, etc.). Confira no PDF se os nomes inclusos estão atualizados. Se algum não estiver correto, descomente a linha correspondente e substitua o nome. Note ainda que alguns títulos têm concordância de gênero (e.g., "Bibliotecário-chefe" vs. "Bibliotecária-chefe"), então lembre-se de ajustar a linha com o título do cargo também. Você pode descobrir quem é o bibliotecário/a-chefe atual no site da biblioteca do INF.

Mais adiante, há uma série de comandos \keyword; estas aparecem como palavras-chave do documento na página do resumo/abstract. Escolher as palavras-chave é uma arte esotérica e misteriosa; consulte seu orientador em caso de dúvida.

A próxima coisa a mudar é a "dedicatória", que costuma ser uma citação de sua preferência. Ela é opcional, mas é uma das partes mais divertidas de escrever a monografia anyway. Logo depois, vem a seção de agradecimentos, que também é opcional (or so they say).

Em seguida, vem o o bloco \begin{abstract}, onde vai o resumo na língua do documento. Não pode haver linhas em branco entre o fim do texto e o \end{abstract}; caso contrário, a compilação do documento dá um erro de "There's no line here to end". Go figure.

A seguir, vem o bloco \begin{englishabstract}{keyword1, keyword2, …}. Apesar do nome, o que vai nesse bloco é o resumo na outra língua: se a monografia for em português, aí vai o abstract em inglês; se a monografia for em inglês, aí vai o abstract em português. O segundo argumento do bloco é a lista de palavras-chave/keywords (as mesmas que você usou antes com os comandos \keyword, mas na outra língua). Também não pode haver linhas em branco antes do \end{englishabstract}.

A seguir, vem a lista de abreviaturas; confira as instruções nos comentários. Note, entretanto, que essa lista deveria estar em ordem alfabética, que eu saiba. Em seguida, há a lista de símbolos, que vem comentada por padrão, mas você pode descomentá-la e preenchê-la de maneira análoga se você usa algum símbolo matemático que requeira explicação no seu documento.

A seguir vêm os comandos \listoffigures, \listoftables e \tableofcontents, que geram automaticamente as respectivas listas. Você não precisa mexer nesses comandos.

Finalmente, vem o texto do documento propriamente dito. No documento de exemplo, há algumas instruções sobre citações, figuras e outros detalhes, que você pode (deve) ler, mas no final você deve apagar tudo desde o \chapter{Introdução} até logo antes do \bibliographystyle e substituir pelo conteúdo da sua monografia. Se você preferir criar arquivos separados para cada capítulo para se organizar, você pode usar o comando:

\input{arquivo.tex}

que é uma espécie de #include, que insere o conteúdo do arquivo especificado nesse ponto do documento. Você pode incluir arquivos em outros diretórios. Por exemplo, você pode criar um diretório capitulos e usar \input{capitulos/introducao.tex}.

A linha \bibliography{nome} diz o nome (sem a extensão) do arquivo onde estão suas referências bibliográficas (no formato BibTeX). Por exemplo, se a linha diz:

\bibliography{biblio}

então o LaTeX vai procurar as referências no arquivo biblio.bib.

Compilando o documento

Se você está usando o ShareLaTeX/Overleaf, tudo o que você tem que fazer é clicar no botão "faz". Há outras IDEs de edição de LaTeX com funcionalidades análogas. Se não é o seu caso, continue lendo.

O comando principal para compilação de documentos LaTeX é o pdflatex. A sintaxe básica é pdflatex arquivo.tex. Se a compilação for bem-sucedida, será gerado um arquivo.pdf com o resultado, bem como um arquivo.log com mensagens do LaTeX e outros arquivo.* usados internamente pelo LaTeX.

Se ocorrer um erro durante a compilação, por padrão o pdflatex cai em um prompt de depuração muito doido. Para sair do prompt, digite x e dê ENTER. Para evitar cair no prompt, use a opção -halt-on-error antes do nome do arquivo.

Outra opção útil é -file-line-error, que precede os erros com nome-do-arquivo:linha:. Alguns editores de texto, como Emacs e Vim, possuem funcionalidades para rodar um comando externo de compilação, coletar as mensagens de erro e saltar diretamente para a linha onde ocorreu o erro, desde que as mensagens de erro contenham a localização do erro em um formato reconhecido (que é o que essa opção faz).

O comando pdflatex realiza apenas uma passada pelo arquivo. Porém, coisas como gerar os números das páginas no índice e números de seção em referências requerem duas passadas pelo arquivo. Além disso, a geração de bibliografia requer a chamada do comando bibtex, e depois disso é necessário rodar o pdflatex novamente (mais duas vezes). O mais prático é escrever um Makefile para rodar todos esses comandos de uma vez. Crie um arquivo chamado Makefile com o seguinte conteúdo:

# As linhas 'export' só são necessárias se você optou por não instalar o
# iiufrgs em um local padrão e não colocar seu documento e os arquivos de
# 'inputs/' no mesmo diretório.
export TEXINPUTS = .:caminho-completo-do-diretório-inputs:
export BSTINPUTS = $(TEXINPUTS)
export BIBINPUTS = $(TEXINPUTS)

PDFLATEX = pdflatex -halt-on-error -file-line-error
FILENAME = nome-do-seu-documento-sem-a-extensão

all:
	$(PDFLATEX) $(FILENAME).tex
	$(PDFLATEX) $(FILENAME).tex
	bibtex $(FILENAME)
	$(PDFLATEX) $(FILENAME).tex
	$(PDFLATEX) $(FILENAME).tex

Note que as linhas indentadas devem ser precedidas de um caractere TAB, não de espaços. Note também que o bibtex recebe o nome do arquivo LaTeX (não o nome do arquivo de bibliografia), sem a extensão.

Feito isso, agora é só rodar make para compilar seu documento. No Vim, você pode usar o comando :make para compilar, e :copen para listar os erros encontrados. No Emacs, você pode usar M-x compile. (No latex-mode do Emacs também há um comando tex-compile, acessível via C-c C-c, que serve para compilar o documento, mas ele não vai usar o Makefile. Eu sou novo nestas terras do Emacs, então não sei bem como isso funciona. Também existe um modo mais avançado de edição de LaTeX chamado AUCTeX, mas não tenho experiência com ele.)

lastpage.sty

O iiufrgs usa um arquivo lastpage.sty para determinar o número da última página, que ele inclui no quadrinho CIP na segunda página do documento. Se você se deparou com o erro:

! LaTeX Error: File `lastpage.sty' not found.

você tem duas opções:

Bibliografia

A maneira normal de trabalhar com referências bibliográficas no LaTeX é através do BibTeX. O iiufrgs vem com um arquivo BibTeX de exemplo, biblio.bib, que aparentemente saiu da coleção pessoal de alguém e foi gerado pelo JabRef (pacote jabref no Debian/Ubuntu/etc), um gerenciador gráfico de bibliografias em BibTeX. Eu nunca usei o JabRef, mas se você gosta de ferramentas gráficas, pode querer experimentá-lo. Caso contrário, continue lendo.

O arquivo .bib é basicamente uma seqüência de entradas bibliográficas. Uma entrada tem mais ou menos essa cara:

@tipo-de-publicação{label-da-sua-escolha,
  title = {Título da Publicação},
  author = {Turing, Alan and Church, Alonzo and Gödel, Kurt},
  year = {2101},
  booktitle = {Proceedings of the 42nd Intergalactic Conference on Computability Theory},
  pages = {123--134},
  publisher = {ACM}
}

tipo-da-publicação é algo como article (artigo em journal), inproceedings (artigo de conferência), book (livro inteiro), incollection (capítulo de livro, quando o autor do capítulo é diferente do autor/editor do livro), electronic (publicação na Internet), ou uma infinidade de outras possibilidades. O tipo de publicação define os campos que podem aparecer dentro da entrada e o formato como ela aparece na bibliografia do PDF gerado.

label-da-sua-escolha é a label que você vai usar com os comandos \cite e afins.

BibTeX é um mundo à parte e eu não vou tentar explicar tudo o que há para explicar aqui; para mais informações, procure por "BibTeX" na Internet. Deixo, entretanto, algumas dicas:

Fim do conto

Acho que era isso. Em um post futuro, pode ser que eu escreva uma introduçãozinha aos comandos de formatação do LaTeX, mas com o que aparece no arquivo de exemplo já dá para se virar um pouco. Você pode dar uma olhada nos outros posts com a tag latex.

Dúvidas, sugestões e correções podem ser deixadas nos comentários.

[30/10/2015: Incluídas informações sobre o Overleaf.]

5 comentários / comments

Main menu

Recent posts

Recent comments

Tags

em-portugues (213) comp (148) prog (71) in-english (62) life (49) unix (38) pldesign (37) lang (32) random (28) about (28) mind (26) lisp (25) fenius (22) mundane (22) web (20) ramble (18) img (13) rant (12) hel (12) scheme (10) privacy (10) freedom (8) esperanto (7) music (7) lash (7) bash (7) academia (7) copyright (7) home (6) mestrado (6) shell (6) android (5) conlang (5) misc (5) emacs (5) latex (4) editor (4) etymology (4) php (4) worldly (4) book (4) politics (4) network (3) c (3) tour-de-scheme (3) security (3) kbd (3) film (3) wrong (3) cook (2) treta (2) poem (2) physics (2) x11 (2) audio (2) comic (2) lows (2) llvm (2) wm (2) philosophy (2) perl (1) wayland (1) ai (1) german (1) en-esperanto (1) golang (1) translation (1) kindle (1) pointless (1) old-chinese (1)

Elsewhere

Quod vide


Copyright © 2010-2024 Vítor De Araújo
O conteúdo deste blog, a menos que de outra forma especificado, pode ser utilizado segundo os termos da licença Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International.

Powered by Blognir.