Elmord's Magic Valley

Software, lingüística e rock'n'roll. Às vezes em Português, sometimes in English.

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.

Comentários / Comments (4)

John Gamboa, 2016-04-04 11:47:04 -0300 #

Báááá...

Eu to procrastinando furiosamente pra prova que tenho amanhã, e então ler isso aqui inteiro agora vai me fazer sentir terrível; mas eu vou certamente manter essa aba aberta pra depois da prova.

Sempre quis brincar com funcionais (e até durante muito tempo quis brincar de verdade com Haskell =S); mas cada vez mais percebo que, simplesmente, programar não é pra mim. Sei lá... fora o "conhecer sobre a linguagem", eu nunca tenho esse saco todo pra resolver que vou fazer um programa supostamente "útil" [apesar de que comecei a fazer um programinha pra mim em python pra treinar meu Hindi e to super empolgado pra continuar -- apesar de que, well, também o fiz enquanto procrastinando furiosamente pra prova de amanhã]

[and then my comment turns full circle]


Vítor De Araújo, 2016-04-06 01:51:27 -0300 #

@John: Que bom que alguém teve interesse. :P Espero que o post/série seja o que tu esteja esperando. Comentários, sugestões, críticas e afins são muito bem-vindos. Eu sempre fico na dúvida se estou apresentando as coisas da melhor maneira, se na minha tentativa de ser beginner-friendly eu não estou explicando coisas demais, se eu estou passando por cima de alguma coisa que não me dei conta que deveria explicar melhor, etc. Enfim, reclame à vontade. :P

Eu já fui muito fascinado por Haskell em 2009~2010, mas aí eu migrei pro mundo Lisp e fiquei mais contente por aqui. :P Haskell é legal, mas eu meio que não tenho muito saco pro paradigma puramente funcional. Eu prefiro as linguagens funcionais impuras, que favorecem mas não obrigam a programar em estilo funcional. Os Lisps em geral têm uma filosofia de não restringir o programador, que é uma coisa que me agrada bastante (embora um sistema de tipos faça falta de vez em quando (embora não um sistema de tipos a la Haskell, mas isso é assunto pra outra hora)).

Espero que tenha dado tudo certo na prova. E boa sorte com o programa em Python!


Cara, 2016-04-19 09:45:59 -0300 #

É cara, os dois primeiros posts até não "são" longos, mas agora o backlog ficou meio gigante. Tenho que ver isso aê... E os comentários estão cada vez melhores, especialmente no segundo post. Essa galere sabe o que tá fazendo.


Graf Cornelius Schaap van Der Grafschaap, 2016-04-19 17:18:52 -0300 #

Como é que esse post escapou da minha visão além do alcance e não tinha comentários ainda? Que vergonha, meu Deus.

Enfim, gostaria de fazer uma correção: não é Python, é Phyton!!!
referência: "Urbosenti: uma arquitetura ubíqua para a proteção dos aldeões italianos de Pádua[FERNANDO ET AL. 2XX8]"


Deixe um comentário / Leave a comment

Main menu

Posts recentes

Comentários recentes

Tags

em-portugues (213) comp (133) prog (65) life (46) in-english (44) unix (33) pldesign (32) lang (31) random (28) about (26) mind (24) lisp (23) mundane (22) web (17) fenius (17) ramble (16) img (13) rant (12) hel (12) scheme (10) privacy (10) freedom (8) academia (7) esperanto (7) copyright (7) music (7) bash (7) lash (7) home (6) mestrado (6) shell (6) conlang (5) misc (5) emacs (4) politics (4) book (4) php (4) worldly (4) editor (4) latex (4) etymology (4) android (4) film (3) kbd (3) security (3) network (3) wrong (3) c (3) tour-de-scheme (3) poem (2) philosophy (2) comic (2) llvm (2) cook (2) treta (2) lows (2) physics (2) perl (1) german (1) wm (1) en-esperanto (1) audio (1) translation (1) old-chinese (1) kindle (1) pointless (1)

Elsewhere

Quod vide


Copyright © 2010-2019 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.