More Emacs - a better eshell
Some Emacs users live their entire lives without ever meeting eshell. Eshell is a command-line shell where you can run programs, list directories, copy files and do all kinds of things you would normally need a terminal window for. Eshell even made a cameo appearance in Tron Legacy, as the command-line interface to Encom's computers. Unfortunately, there is something it won't do: it's not easy to start more than one eshell session - when you invoke eshell for the second time, it goes back to the buffer the first eshell opened, normally called "*eshell*". I could invoke the "rename-uniquely" command manually after opening each eshell window (or before attempting to open a new one), but that's annoying (and I ofen forget it).
I keep eshell bound to C-$ (control-$ for the emacs-illiterate ou ^$ for really old-school folks - not that any keyboard I know of emits valid ASCII when someone presses Control-$). I often need more than one shell. "ansi-term" will do the buffer-renaming magic and I can start a number of those, but that's not really what I want - ansi-term runs the shell as a separate process. I use it when I need things I'd need another terminal for, like input and output redirection, but I'd prefer to use eshell when all I need is another shell.
Unfortunate people who don't use Emacs will think I would need to dig up the sources of eshell to add the rename-uniquely invocation and change them. I'll remind those poor folks than your init.el file is, in fact, executed when Emacs starts and, therefore, all I need to do is to change my binding from
(global-set-key (kbd "C-$") 'eshell)
to
(global-set-key (kbd "C-$") '(lambda () (interactive) (eshell) (rename-uniquely) ))
In case you are wondering, the "interactive" function is invoked to mark this function as a command. If I didn't do it (as I foolishly did earlier this morning, before reading this) I'd be greeted with
Wrong type argument: commandp, (lambda nil (eshell) (rename-uniquely))
That's nice, but not perfect. If you invoke it, you will notice your first eshell buffer is named "*eshell*<2>". That's bad.
It happens because the buffer is uniquely renamed every time it's created, not only when there already is a buffer named "*eshell*". For that, we need a little more code:
(global-set-key (kbd "C-$") '(lambda () (interactive) (if (member "*eshell*" (mapcar* 'buffer-name (buffer-list))) (progn (eshell) (rename-uniquely)) (eshell))))
OK. That was stupid. We test whether there is already a buffer named "*eshell*" and then we try to invoke eshell to create a new one, forgetting that eshell will also find the buffer called "*eshell*" and switch to it rather than creating a new one. We then, dumbly, rename our only eshell buffer.
This thing needs more brains.
Since I don't expect to have more than a handful eshells running (you really shouldn't try to), progressing through numbered buffer names until we find a free one should be a fine approach:
(global-set-key (kbd "C-$") '(lambda () (interactive) (let ((i 1) (found-a-name nil)) (while (not found-a-name) (setq buffname (concat "*eshell*<" (int-to-string i) ">")) (setq found-a-name (not (member buffname (mapcar* 'buffer-name (buffer-list))))) (if found-a-name (eshell i) (setq i (+ i 1)) )))))
Now we start from i = 1 and check if there is already a buffer named "*eshell*<i>". If there is not, create one with that name (passing the number to eshell) and end our search. This is doubly pretty, because the first solution would rename our first eshell as "*eshell*<2>". With this one, the first eshell is "*eshell*<1>", which is elegant. If you manually open an eshell, it'll be called "*eshell*", distinguishing it from our automatically named shells.
Still, as a friend of mine well pointed out, the code is really ugly. It'll also fail if the default name for eshell buffers is changed. In short, it's a mess. I felt compelled to do better:
(global-set-key (kbd "C-$") '(lambda () (interactive) (let ((i 1)) (while (member (concat eshell-buffer-name "<" (int-to-string i) ">") (mapcar* 'buffer-name (buffer-list))) (setq i (+ 1 i))) (eshell i))))
Much more concise and to-the-point. I like it.
Can you customize Eclipse like that?
A bug. Yes, they hit me too
After publishing this, I noticed the variable eshell-buffer-name is not defined until after you invoke eshell for the first time. If you try to C-$ on a freshly started Emacs session, you'll get a
Symbol's value as variable is void: eshell-buffer-name
message. In order to fix this, the code must check whether eshell-buffer-name is bound and, if it's not, we start the buffer giving it a 1.
(global-set-key (kbd "C-$") '(lambda () (interactive) (let ((i 1)) (if (boundp 'eshell-buffer-name) (progn (while (member (concat eshell-buffer-name "<" (int-to-string i) ">") (mapcar* 'buffer-name (buffer-list))) (setq i (+ 1 i))) (eshell i)) (eshell 1)))) )
OK. Now I am satisfied.
Edit: And now, I feel stupid
A friend of mine, very politely, possibly to avoid embarrassing me in public, sent me an e-mail pointing out he didn't quite understood what I was trying to accomplish here. In his message, he pointed out I could just invoke (eshell t). When passed "t" (boolean true in Lisp) as a parameter, eshell does precisely what I wanted it to do. So, the new version in my init.el is even shorter:
(global-set-key (kbd "C-$") '(lambda () (interactive) (eshell t)))
Well... At least I learned something.
Emacs perfection - selecting fonts according to screen size
Most of the time, I work at my desk, where my netbook is hooked up to a reasonably sized monitor (the largest its feeble GPU can handle with acceleration). Since screen real-estate in that situation is abundant, I opted to use a larger font (one I made from the x3270 bitmap font, but that's another, much longer story). Unfortunately, when I am not at my desk, the 1024x600 LCD is quite limiting and the default options don't work for me. When I am away from the big screen monitor, screen real-estate is limited and a small font should be selected by default.
I started from the options the Custom menu gave me. Removing comments, it's a very simple snippet:
(custom-set-faces '(default ((t (:inherit nil :stipple nil :inverse-video nil :box nil :strike-through nil :overline nil :underline nil :slant normal :weight normal :height 140 :width normal :family "IBM 3270")))) '(linum ((t (:inherit default :foreground "#777" :height 110)))))
Translating that to English, I have a font called "IBM 3270" at a 14-point height, with line numbers with 11 points. Very readable.
But that doesn't solve the problem when I am on the road.
The friendly guys at Stack Overflow pointed out one way to do it: x-display-pixel-width and x-display-pixel-height.
With that in hand, I can do:
(if (> (x-display-pixel-width) 1280) ; screen is big (custom-set-faces '(default ((t (:inherit nil :stipple nil :inverse-video nil :box nil :strike-through nil :overline nil :underline nil :slant normal :weight normal :height 140 :width normal :family "IBM 3270")))) '(linum ((t (:inherit default :foreground "#777" :height 110))))) ; screen is small (custom-set-faces '(default ((t (:inherit nil :stipple nil :inverse-video nil :box nil :strike-through nil :overline nil :underline nil :slant normal :weight normal :height 110 :width normal :family "IBM 3270")))) '(linum ((t (:inherit default :foreground "#777" :height 80))))) )
and I can be happy.
But can I?
When I start Emacs without X (as in from a remote terminal), these functions issue an ugly warning telling me that X is not available and that I should --debug-init and fix the problem. That's safely ignorable (as these adjustments are being done when everything non-cosmetic is already in place, at least in my init.el), but, still, annoying.
There is a variable, window-system, that can help - it holds "x" if we are under the X windowing system and nil if we are using a character terminal. With it, I can do:
(if (and (eq 'x window-system) (> (x-display-pixel-width) 1280)) ; screen is big (custom-set-faces '(default ((t (:inherit nil :stipple nil :inverse-video nil :box nil :strike-through nil :overline nil :underline nil :slant normal :weight normal :height 140 :width normal :family "IBM 3270")))) '(linum ((t (:inherit default :foreground "#777" :height 110))))) ; screen is small (custom-set-faces '(default ((t (:inherit nil :stipple nil :inverse-video nil :box nil :strike-through nil :overline nil :underline nil :slant normal :weight normal :height 110 :width normal :family "IBM 3270")))) '(linum ((t (:inherit default :foreground "#777" :height 80))))) )
short-circuiting the x-display-pixel-width and allowing Emacs starts cleanly from a remote terminal session. Since window-system also can tell you if you are on a Mac (or NeXT, if you are into retrocomputing) or under Windows (you can't argue taste), you can take appropriate actions according to your environment.
I am happy for today.
A prova do GDD, em Lisp
Outro dia assisti a uma palestra sobre Hadoop e MapReduce. Todos os exemplos foram dados em um pseudocódigo que misturava Ruby com JavaScript enquanto, na minha cabeça, eu os re-escrevia de um jeito que funcionassem de verdade em Python (eu trabalho com isso). Como no meu último post eu já resolvi a provinha do Google Developer Day usando Python (e usei uma função anônima e um filter, vejam só), eu fiquei com a coceira enorme de refazer essas perguntas com Lisp (já que ela expressa maps e reduces como ninguém mais).
No meu laptop eu tenho uns 4 dialetos de Lisp diferentes (não existe uma "implementação canônica" como no caso de Python ou Ruby). Para brincar aqui, eu escolhi Clojure. Clojure é um dialeto de Lisp que roda sobre a máquina virtual do Java (Java é o COBOL do século XXI e provavelmente do XXII, mas a VM e toda a infra-estrutura dela são muito legais). Outro motivo é que o meu Emacs (o IDE to rule them all) está ligado ao Clojure pelo Slime. Eu instalei ambos (Clojure e Slime) com o ELPA, o gerenciador de add-ons do Emacs e, com uma gambiarra e dois symlinks, estou usando o Clojure 1.2 em vez do 1.1 (que é a versão que o ELPA oferece por hora). Eu podia ter tentado usar o Emacs Lisp (o Emacs é quase todo escrito em Emacs Lisp e um REPL dele está sempre disponível), mas ele não é nem um Lisp moderno, nem particularmente gostoso de usar. Podia ter usado Scheme (e provavelmente o resultado seria mais elegante), mas achei que, por rodar na JVM, Clojure é mais "sexy". Qualquer coisa que evite que alguém tenha que escrever código em Java é uma Coisa Boa que merece ser incentivada.
Aqui cabe uma observação: várias linguagens - Scala, Jython, JRuby - permitem que seus programas rodem dentro de máquinas virtuais Java. Todas elas permitem, também, que você use classes que fazem parte tanto do Java propriamente dito como de outros programas nos quais você talvez precise adicionar alguma funcionalidade. A produtividade individual de um desenvolvedor experiente usando uma linguagem como Python e Ruby (dos quais Jython e JRuby são dialetos) já foi observada como sendo superior à de um desenvolvedor igualmente experiente usando Java. Em outras palavras: se você está usando Java você devia dar uma olhada em linguagens mais produtivas.
E isso vale para C#/.NET. Ninguém deveria usar essas tranqueiras.
A propósito, se você quiser ver a resolução da provinha em Jython, vá à outra versão do artigo. É absolutamente a mesma coisa.
Voltando à prova, resolvê-la deu um pouco de trabalho, provavelmente porque eu ainda estou começando a aprender Clojure (o Clojure Programming/Examples/Cookbook e o livro Programming Clojure, de Stuart Halloway tem ambos sido companhia constante aqui - e são bons mesmo como uma introdução ao Lisp - e o The Joy of Clojure é o próximo no wish-list, para assim que estiver pronto) e porque lembro muito pouco de outros dialetos de Lisp (e o pouco que eu lembro, parece, mais atrapalha do que ajuda).
Aviso muito importante: Eu disse isso acima, mas preciso repetir: estou aprendendo Clojure. Posso ter feito alguma barbaridade sem saber. Mais: não fiz qualquer tentativa de otimizar o código exceto para concisão e legibilidade. Também não fiz TDD. E, por favor, não considere meu estilo pessoal como algo que seja bem-vindo (ou mesmo socialmente aceito) entre programadores Lisp ou Clojure. Aceite meus conselhos por sua conta e risco e não me culpe de nada.
Agora que você foi adequadamente avisado, vamos à provinha:
1) O que esse programa faz?
x = 7 y = 4 if y > 2, then y = y * 2 else x = x * 2 print (x + y)
No REPL do Clojure, eu fiz assim:
user> (def x 7) #'user/x user> (def y 4) #'user/y user> (if (> y 2) (def y (* 2 y)) (def x (* 2 x))) #'user/y user> (+ x y) 15
O programa do exercício imprime "15".
2) Quantas vezes esse programa imprime "hello"?
Na linguagem do exercício os loops incluem as extremidades.
for i = 1 to 5 if i != 2, then for j = 1 to 9 print 'hello'
Vou fingir que, de olhar o pseudocódigo, não dá pra sacar que o programa roda 4 vezes o loop interno e imprime "hello" 36 vezes.
Em Clojure, não temos loops como em Python (ou C, ou quase qualquer outra linguagem desse time). Vamos usar uma list comprehension com o efeito colateral de imprimir coisas na tela.
(for [ i (range 1 6) j (range 1 10) ] (if (not= i 2) (println "hello") ) )
Ou, de um jeito um pouco mais Clojure, usando a cláusula ":when" no binding dos ranges no lugar do "jeito C de fazer" com o if dentro do bloco:
(for [ i (range 1 6) j (range 1 10) :when (not= i 2) ] (println "hello") )
Executando isso no REPL, ele vai imprimir os "hello"s (como o exemplo em Python fez) entrecortados pelo retorno da list comprehension (que o Python não fez) e isso é chato de contar (ainda mais chato do que o exemplo do Python). Do mesmo jeito que eu fiz com Python, eu vou usar um contador. Como o Clojure abraça a idéia de programação sem efeitos-colaterais e memória transacional, você precisa envolver a coisa cujo estado você pretende alterar em um pouco de burocracia. Famos criar um contador mutável que pode ter seu valor alterado durante a execução do programa.
(def contador (ref 0))
E, com isso, nosso código fica assim:
user> (def contador (ref 0)) (for [ i (range 1 6) j (range 1 10) :when (not= i 2) ] (println (dosync (alter contador inc)) "hello") )
A função "dosync" cuida da transação (eu poderia alterar várias variáveis de uma vez) e "alter" faz a alteração propriamente dita do dado que está na referência "contador". "dosync" nos devolve o resultado da última coisa que ele fez, que foi incrementar o contador. Agora, na janela do REPL, vemos algo como:
32 hello 33 hello 34 hello 35 hello 36 hello nil nil nil nil nil nil nil nil nil nil) user>
Que é exatamente o que nós queríamos. Agora sabemos que o programa imprime "hello" 36 vezes. Se tivéssemos compilado o programa e o executado a partir de um shell, não veríamos os "nil"s (que só são mostrados pelo REPL porque são o retorno de da função println agrupados pelo for - que retorna uma lista com os resultados).
Poderíamos ter sido mais espertos e simplesmente contado o comprimento da lista que o for nos deu (o monte de "nil"s que atrapalha a leitura da nossa saída), mas isso é para o próximo exercício.3) Quais números, entre 5 e 2675 são pares e divisíveis por 3?
O exercício diz que eu posso escrever um programa para isso. Eu não fiz isso com Python e ainda não preciso fazer isso com Lisp. Tenho uma pontinha de dó de quem precisa.
user> (count (for [n (range 5 2676) :when (and (= (mod n 3) 0) (even? n))] n)) 445
Que foi porco da primeira vez e continua porco agora. Quem leu o primeiro post (ou estava acordado no ginásio) sabe que se um número é par e divisível por 3, ele é divisível por 6. Assim, podemos usar o mesmo truque que usamos com Python na nossa solução:
user> (count (for [n (range 5 2676) :when (= (mod n 6) 0)] n)) 445
Eu só sinto saudade da função "even?" da primeira implementação. É legal.
4) Números bonitos
A pergunta nos apresenta Barbara e seus critérios para que números sejam ou não bonitos. Para ela, números são bonitos se contiverem um dígito 4 e não contiverem um dígito 9. Ao final, nos pergunta quais números entre 14063 and 24779, inclusive, são bonitos.
Agora eu vou usar um pedaço do Clojure que não vem carregado, mas que é muito útil: uma biblioteca de manipulação de strings. Para usá-la, vamos fazer:
user> (use 'clojure.contrib.string) WARNING: repeat already refers to: #'clojure.core/repeat in namespace: user, being replaced by: #'clojure.contrib.string/repeat WARNING: butlast already refers to: #'clojure.core/butlast in namespace: user, being replaced by: #'clojure.contrib.string/butlast WARNING: reverse already refers to: #'clojure.core/reverse in namespace: user, being replaced by: #'clojure.contrib.string/reverse WARNING: get already refers to: #'clojure.core/get in namespace: user, being replaced by: #'clojure.contrib.string/get WARNING: partition already refers to: #'clojure.core/partition in namespace: user, being replaced by: #'clojure.contrib.string/partition WARNING: drop already refers to: #'clojure.core/drop in namespace: user, being replaced by: #'clojure.contrib.string/drop WARNING: take already refers to: #'clojure.core/take in namespace: user, being replaced by: #'clojure.contrib.string/take nil
Os warnings dizem que ela mudou o comportamento de algumas coisas que já estavam disponíveis. Não se preocupe com isso: só queremos a função "substring?". Agora, para saber quantos são bonitos, vamos começar com uma função que decide se um número é bonito ou não:
user> user> (defn bonito? [x] (and (substring? "4" (str x)) (not(substring? "9" (str x))) )) #'user/bonito?
Por favor, ignore que eu converti x duas vezes para uma string. Eu otimizo para legibilidade. Outra coisa: eu poderia ter usado .indexOf diretamente ou usá-lo para fazer meu próprio "substring?", mas como clojure.contrib é parte do Clojure, eu acho melhor não duplicar o que é parte do pacote.
Agora temos um "bonito" no nosso namespace. Vamos testá-la, para ver se fizemos tudo direito:
user> (bonito? 4) true user> (bonito? 9) false user> (bonito? 49) false user> (bonito? 1941) false
Daí, basta aplicá-la usando a mesma técnica que usamos no problema anterior:
user> (count (for [ n (range 14063 24780) :when (bonito? n)] n)) 3047
Se você preferir, pode dispensar a função:
user> (count (for [ n (range 14063 24780) :when (and (substring? "4" (str n)) (not(substring? "9" (str n)))) ] n))
E tem um jeito ainda mais curto, usando a função "filter".
user> (count (filter bonito? (range 14063 24780))) 3047
E, se eu quiser tornar a coisa um pouco mais obscura usando uma função anônima:
user> (count (filter (fn [n] (and (substring? "4" (str n)) (not(substring? "9" (str n))))) (range 14063 24780))) 3047
Apesar das aparências, funções anônimas não existem para atrapalhar a legibilidade do seu código mas para que você possa construí-las somente quando necessário e passá-las como parâmetro para outras funções que vão fazer coisas com elas.
5) Os telefones
A última pergunta nos apresenta um país em que os números de telefone têm 6 dígitos. Números não podem ter dois dígitos consecutivos idênticos, porque isso é caído. A soma dos dígitos tem de ser par, porque isso é legal e o último dígito não pode ser igual ao primeiro, porque isso dá azar.
Vamos começar com os caídos. Ao contrário do nosso exemplo em Python, eu preciso me preocupar com os limites dos índices. (nth "abcdef" -1) não é aceitável em Clojure.
user> (defn caido? [n] (some true? (for [i (range (- (count (str n)) 1))] (= (nth (str n) i) (nth (str n) (+ 1 i))) ))) #'user/caido?
Camos fazer alguns testes para ver se estamos encontrando números caídos:
user> (caido? 123456) nil user> (caido? 122345) true user> (caido? 123451) nil
Observe que, ao contrário da solução em Python (que tem um bug, aliás) a função que identifica números caídos não identifica números que não são caídos mas que dão azar como se fossem caídos.
Vamos precisar, então, escrever uma função para os que dão azar. Essa é fácil:
user> (defn da-azar? [n] (= (first (str n)) (last (str n)))) #'user/da-azar?
Só para ter certeza do que fizemos:
user> (da-azar? 123456) false user> (da-azar? 623456) trueE, finalmente, uma para os números legais:
user> (defn legal? [n] (even? (reduce + (map (fn [s] (Integer/parseInt s)) (for [c (str n)] (str c)))))) #'user/legal?
Aqui eu preciso dizer uma coisa: tem que existir um jeito melhor de invocar o método estático parseInt de Integer. Essa cicatriz entre o lado Lisp e o lado Java está muito feia.
Voltando ao exercício, vamos testar nossa função para termos certeza de que não fizemos nada errado:
user> (legal? 12) false user> (legal? 55) trueCom as três na mão, basta usarmos duas no nosso critério:
user> (count (for [n (range 100000 1000000) :when (and (legal? n) (not (da-azar? n)) (not (caido? n)))] n)) 238500
Mas o exercício não perguntou quantos caídos entre 100000 e 999999. Eles nos deu uma lista. Com um pouco de mágica de clipboard, colocamos os números em uma string, que quebramos e fazemos uma lista:
user> (def tudo "214966 215739 220686 225051 225123 (...) 720202 720568 720576 ") #'user/tudo user> tudo "214966\n215739\n220686\n225051\n225123\n...720202\n720568\n720576\n" user> (def numeros (split-lines tudo)) #'user/numeros user> numeros ("214966" "220686" "225051" "225123"..."720202" "720568" "720576") user> (count numeros) 200
Então, usamos a lista e chegamos no resultado:
user> (count (for [n numeros :when (and (legal? n) (not (da-azar? n)) (not (caido? n)))] n)) 61
61 de 200 é o resultado que tivemos antes. Se erramos, erramos duas vezes do mesmo jeito.
Não doeu tanto assim, doeu?
Muitos programadores morrem de medo de Lisp. Morrem de medo, vêm um monte de parênteses, acham confuso e desistem. Lisp é uma linguagem muito interessante - a mais antiga ainda em uso - e impõe uma certa disciplina que é útil para qualquer programador. Robert "Uncle Bob" Martin recomenda que todo programador profissional aprenda uma linguagem "diferente" das que conhece por ano. A analogia que ele faz é interessante: um carpinteiro que só sabe fazer banquinhos de pinho não é um bom carpinteiro. Ele precisa aprender a trabalhar com outros tipos de madeira e a fazer outros tipos de móvel.
Se tudo o que você sabe fazer são sites em PHP ou módulos de ERP em C#, talvez seja hora de pensar em aprender algo novo.
Música em Scheme
Para quem não sabe o que está aí no alto, os blocos representam S-expressions de um programa em Scheme que toca determinados samples em momentos diferentes, segundo condições determinadas pelo próprio programa. O que vemos no vídeo é o programa sendo alterado durante a performance usando o Scheme Bricks.