Review: Logicomix

Agosto 12, 2012

Logicomix é uma estória em quadrinhos sobre a busca do fundamento da matemática. É um livro fantástico porque mistura romance, história, mitologia e matemática. Além disso é uma meta-estória, pois relata o processo de desenvolvimento dos quadrinhos na própria estória.

Um dos autores (e personagem) do livro é Christos Papadimitriou, atualmente professor de teoria da computação em Berkeley. Durante minha iniciação científica cheguei a estudar um de seus livros escrito com Ken Steiglitz sobre Otimização Combinatória, mas achei muito difícil e acabei usando outro :P

Teóricos da computação fazem muito uso da lógica, especialmente os que trabalham com complexidade computacional e computabilidade. A estória dá a entender que Papadimitriou foi convidado como o especialista em lógica, embora o autor principal, Apostolos Doxiadis tenha formação matemática também.

Spoiler! O restante deste post contém um breve resumo do livro, além de uma análise pessoal.

Resumo

Tractatus Logico-Philosophicus

Bertrand Russell é o personagem central do livro que descreve sua saga em busca dos fundamentos da matemática.

Russell aparece em dois planos: em um está no presente, palestrando a um grupo de americanos contrários à adesão dos Estados Unidos à Segunda Guerra Mundial. Durante essa palestra ele conta a história de sua vida, que acontece em um outro plano. A grande maioria dos acontecimentos acontece nesse segundo plano e o Russell do plano presente serve mais como um narrador.

Além desses dois planos temos o plano em que os autores do livro estão elaborando a estória ao mesmo tempo em que ela acontece. Eles geralmente aparecem para justificar alguma discrepância entre a história e a estória, além de explicar conceitos mais técnicos.

Logo criança, Russell torna-se órfão e passa a viver com seus avós na mansão Pembroke. É apresentado aos trabalhos de Euclides por um professor particular de matemática.

Russell segue para o Trinity College, onde conhece Alfred Whitehead. Casa-se com Alys Smith e então fazem uma viagem ao continente, passando primeiramente pela Alemanha, onde conhecem Gottlob Frege e George Cantor (embora o livro deixe claro que Russell não os conheceu pessoalmente).

Na França, conhecem a esposa de Whitehead, Evelyn Wade. A estória retrata um interesse de Russell em Evelyn, embora em nenhum momento do livro pareça ter havido algo mais entre eles. Depois, Russell participa do congresso internacional de matemáticos onde David Hilbert divulga a famosa lista de 23 problemas abertos em matemática.

Neste ponto Russell descobre um paradoxo na teoria dos conjuntos, que abala o mundo da matemática. Whitehead e Russell decidem então começar a escrita do Principia mathematica, que se mostra uma tarefa desgastante.

Russell passa a dar aulas em Cambridge e é quando Ludwig Wittgenstein torna-se seu estudante de doutorado. Evelyn, a esposa de Whitehad acha que está prestes a morrer e pede para Russell consolar seu filho, Eric. Cansado de seus trabalhos com a lógica, Russell sente-se reconfortado por exercitar sentimentos como compaixão e amor para com Eric.

Começa a primeira Guerra Mundial. Russell se posiciona contra a adesão do Reino Unido, o que acaba o levando à prisão. Wittgenstein se alista ao exército Autro-Húngaro enquanto Eric, filho de Whitehead, faz o mesmo pelo lado britânico. Wittgenstein sobreviveu à guerra, fazendo-o mudar sua maneira de ver o mundo. Por outro lado, Eric é abatido em combate.

Após o fim da primeira guerra, Wittgenstein estava impedido de ir à Inglaterra por ser austríaco e portanto se encontra com Russell em território neutro: Haia, Holanda. Nesse encontro Wittgenstein contrapõe diversas ideias de Russell.

Russell casa-se pela segunda vez com Dora Black, tendo o seu primeiro filho. Seu casamento era bastante liberal, sendo que ele aceitava que amantes frequentassem sua casa. Seus pais também tiveram um casamento desta forma. Juntos decidiram fundar uma escola progressista para crianças, a Beacon Hill. À mesma época, Wittgenstein começa a dar aulas em escolas primárias.

O matemático Moritz Schlick passa a organizar o Círculo de Viena, uma associação de filósofos que se encontravam em torno da Universidade de Viena. Em uma dessas reuniões, Russell conhece Kurt Gödel e John Von Neumann e foi nesse encontro que Gödel anunciou seu primeiro teorema de incompletude, destruindo o sonho de Russell de definir um conjunto de axiomas que pudesse demonstrar todas as verdades matemáticas. Em seguida, as influências nazistas começaram a se espalhar pelo continente e em 1936, Schlick foi assassinado por um fanático nazista, o que pôs fim ao círculo e é nesse ponto que Russell conclui sua narrativa.

A estória termina voltando para o plano dos autores, que vão assistir à participação de Anne Bardy em uma encenação da peça Oresteia.

Matemática

Das diversas referências matemáticas presentes no livro, gostaria de comentar sobre as três que achei mais interessante.

Paradoxo de Russell

Em 1901, Russel apresenta um paradoxo da teoria dos conjuntos. Esse paradoxo por ser melhor entendido através de um exemplo, o paradoxo dos barbeiros.

Considere uma cidade onde existe apenas um barbeiro e uma lei que diz que o barbeiro deve fazer a barba de todos os homens que não se barbeiam sozinhos, mas não pode fazer a barba de quem se barbeia sozinho.

Consideremos duas possibilidades: o barbeiro se barbeia sozinho ou não. Se ele se barbeia sozinho, está infringindo a lei, porque ele não pode barbear quem se barbeia sozinho. Por outro lado, se ele não se barbeia, a lei o obriga a fazer sua própria barba. Conclui-se que a lei é contraditória.

A generalização para a teoria dos conjuntos é a seguinte: seja R conjunto de todos os conjuntos que não contém a si mesmos. Pergunta-se: R está contido em si mesmo? Vamos analisar duas possibilidades. Se R está contido em si mesmo, então R é um conjunto que contém a si mesmo e não poderia estar em R. Por outro lado, se R não está contido em si mesmo, ele é um conjunto que não contém a si mesmo e portanto deveria ser incluído em R :)

O Infinito e a Teoria dos Conjuntos

Em Logicomix, Russell descreve o paradoxo do hotel de Hilbert que exemplifica o quão contra-intuitivo é trabalhar com o conceito de infinito. A ideia por trás do paradoxo pode ser usada para provar, por exemplo, que existe uma bijeção entre os números naturais pares e os números naturais!

Isso está relacionado com o trabalho de George Cantor, que criou os conceitos de conjuntos enumeráveis e não enumeráveis, sendo os enumeráveis aqueles com mesma cardinalidade dos números naturais, ou seja, para os quais existe um mapeamento um pra um de seus elementos para com os números naturais.

Assim como os números naturais pares têm mesma cardinalidade dos números naturais, Cantor demonstrou que os números inteiros são enumeráveis assim como o conjunto dos números racionais! O conjuntos dos números reais não é enumerável.

Os 23 problemas de Hilbert

David Hilbert divulgou uma lista de 23 problemas não resolvidos à época de 1900. Há três problemas sobre os quais gostaria de comentar.

O principal problema da lista, e que até hoje não foi resolvido, é o problema 8, que é demonstrar a Hipótese de Riemann. O livro The music of primes, fornece uma introdução bem acessível sobre a hipótese, a função zeta de Riemann e a relação com números primos.

Um problema importante também é o número 2, provar que os axiomas da aritmética são consistentes. O segundo teorema da incompletude de Gödel mostra que não é possível provar esse teorema usando a própria aritmética. Entretanto, não há um consenso se isto é uma solução ou não para o problema.

Finalmente, um problema que eu acho particularmente interessante é o número 10: encontrar um algoritmo que determina se uma equação Diofantina polinomial com coeficientes inteiros tem uma solução.

Gosto desse problema porque é fácil de enunciar, está relacionado à área que pesquisei no mestrado (programação linear inteira) e porque foi resultado de um trabalho longo e de contribuição de diversas pessoas, Martin Davis, Yuri Matiyasevich, Hilary Putnam e Julia Robinson.

Mitologia

Algumas referências mitológicas aparecem na estória. A primeira é a um mito indiano sobre a tartaruga que segura o mundo. Russell faz a analogia como o mundo sendo a matemática e a tartaruga seus fundamentos.

Em certo ponto Whitehead mostra a Russell o quadro “As Danaides” de John Waterhouse. As Danaides são as 50 filhas de Dánao, que estavam prometidas aos 50 filhos de Egipto, seu irmão gêmeo. Todas exceto Hipernestra assassinaram seus esposos na noite de núpcias e como punição foram obrigadas por Hades a encherem um jarro de água com furos durante toda a eternidade.

As Danaides: pintura a óleo de John Waterhouse

Whitehead usou o mito como metáfora para o trabalho do Principia Mathematica. Russell queria encontrar um fundamento para a matemática, mas sempre via necessidade de fundamentar cada base que encontrava. Como se disse no livro, ele estava procurando a base da tartaruga que segurava o mundo, mas encontrou uma torre infinita de tartarugas.

Finalmente, temos a encenação peça Oresteia, uma trilogia escrita pelo dramaturgo grego Ésquilo.

O livro retrata a terceira peça, Eunêmides, no qual as Erínias, deusas da vingança decidem punir Orestes. Atena intervém e decide que o futuro de Orestes deve ser decidido por um júri popular.

Literatura e Teatro

Alguns clássicos da literatura e teatro aparecem na estória. O avô de Russell possuia uma biblioteca onde Bertie descobriu a Divina Comédia de Dante Alighieri, que foi chamado de o livro proibido.

Em Cambridge lê Pais e Filhos, de Ivan Turguniev. Segundo a Wikipédia, o protagonista do livro é Bazárov, um jovem intelectual materialista, que nega o amor, a arte, a religião e a tradição, e diz acreditar apenas em verdades cientificamente comprovadas pela experiência. Essa negação da religião e tradição podem ser encontradas na personalidade de Russell na estória.

A estória também retrata a peça de teatro Espectros, do norueguês Henrik Ibsen. Há um destaque para a frase “os pecados dos pais se repetem nos filhos”. Talvez seja uma referência ao estilo de vida dos pais de Russell, que acabou se repetindo em seu casamento com Dora.

Russell passa a recitar versos do poema Alastor, de Percy Shelley. Segundo a Wikipedia, Alastor conta a história de um poeta que persegue a parte mais obscura da natureza em busca de “verdades estranhas em terras desconhecidas”. Podemos enxergar um paralelo aqui com a busca pelos fundamentos da matemática por porte de Russell.

Ao conhecer Alys, Russell cita diversos personagens do livro Alice no País das Maravilhas, de Lewis Carroll, como o Tweedledee, Tweedledum, Gato de Cheshire e o Chapeleiro maluco.

Após o fim da Primeira Guerra Mundial Russell demonstra seu descontentamento com o dadaísmo e cita versos do poema The Second Coming, the William Butler Yeats, que refletem sua opinião.

Conclusão

Gostei bastante deste livro e recomendo a quem gosta de lógica e história da matemática. Eu particularmente não sou muito fã de história em quadrinhos, achei que se fosse em prosa a estória poderia conter mais detalhes. Lendo sobre a vida dos principais personagens na Wikipedia, penso que faltou um pouco de coesão nos fatos relatados.

Achei excelente inserir referências a clássicos da literatura para ajudar a caracterizar a personalidade de Russell. Fico feliz de ter escrito esse resumo e análise porque acho que consegui enxergar alguns detalhes que tinham passados despercebidos na primeira leitura.

Interpretei o fato da estória ser uma meta-estória como uma alusão ao paradoxo de Russell, que tem como base conjuntos que contêm a si mesmos. No caso do Logicomix é uma estória que contém a si mesma.

A encenação de Orestéia me pareceu desconexa com o restante da estória. Talvez a ideia fosse mesmo contar duas estórias independentes (a de Bertrand e a dos autores).

Leitura adicional

Alguns livros e links que eu selecionei para o aprofundamento no conteúdo do Logicomix. Na verdade só li o primeiro e é o único que posso recomendar. Os outros estão na minha pilha de leitura.

[1] The Music of the Primes de Marcus du Sautory – Já li, e gostei muito. Fala sobre os 23 problemas de Hilbert e a hipótese de Riemmann de uma maneira bastante acessível.
[2] Alice no país das maravilhas de Lewis Carroll – Lewis Carroll era um matemático e lógico. Este livro é mencionado no Logicomix.
[3] Scott Aaronson – Filosofia e Ciência da Computação Teórica, Scott Aaronson é um professor de Teoria de Computação no MIT e conhecido por seu trabalho em Computação Quântica.


Paul Graham e Combinatores em Haskell

Agosto 5, 2012

Paul Graham é um ensaísta, programador e investidor. É graduado em Cornell e possui doutorado por Harvard [1].

Como ensaísta, escreveu diversos artigos sendo o mais famosos deles o “A plan for spam” sobre o uso de um filtro Bayesiano para combater spam.

Como programador, Graham é conhecido por seu trabalho com Lisp, tendo escrito livros sobre esta linguagem e vem desenvolvendo um dialeto chamado Arc. Também desenvolveu a primeira aplicação web chamada Viaweb, posteriormente adquirida pela Yahoo!

Como investidor, fundou em 2005 a empresa Y Combinator que faz investimentos em start-ups. Algumas das empresas financiadas pela Y-Combinator são: Dropbox, Airbnb, reddit e Posterous.

Sobre o nome da empresa, eis uma tradução livre do FAQ deles:

Porque vocês escolheram o nome “Y Combinator?”

O combinador Y é uma das ideias mais legais em ciência da computação e é também uma metáfora para o que nós fazemos. É um programa que roda programas; é uma empresa que ajuda a abrir empresas.

Neste post gostaria de apresentar algumas notas de estudos sobre combinadores em Haskell, descrevendo os principais tipos de combinadores incluindo o tipo Y.


Combinadores em Haskell

Esse post teve origem durante os estudos do Capítulo 9 do Real World Haskell.

Em certo ponto do capítulo o autor menciona o termo de combinadores, não deixando muito claro o que são e para que servem. Fui pesquisar um pouco sobre o assunto e decidi escrever um post.

Um combinador pode ser definido como uma função lambda que não tem variáveis livres. Uma variável livre é uma variável que não foi passada como parâmetro na função lambda. Alguns exemplos de funções lambda em Haskell:

1) A função abaixo é um combinador pois a variável x está no parâmetro

\x -> x

2) No exemplo a seguir y é uma variável livre e portanto não é um combinador.

\x -> x y

3) Aqui ambos x e y foram passadas por parâmetro, então temos um combinador.

\x y -> x y

4) O exemplo abaixo não é um combinador porque f não foi passado como parâmetro

\x y -> f (x y)

5) A seguir temos uma pegadinha. Observe que o operador + é na verdade uma função que recebe dois argumentos, como no exemplo (4). Porém como o operador não foi passado como parâmetro, também não é um combinador.

\x y -> x + y 

Simplificadamente, podemos pensar em um combinador como uma função que recebe funções e retorna outra função resultado da combinação das funções de entrada.

Tipos de combinadores

Nas próximas seções, vamos apresentar alguns dos combinadores mais conhecidos. Esses combinadores têm como nome uma única letra maiúscula. Porém, no livro To Mock a Mocking Bird [2], Raymond Smullyman apelida os diferentes tipos de combinadores com nomes de pássaros. Por isso, cada seção terá o nome do combinador e entre parênteses o apelido dado por Smullyman.

Combinador I (Identity Bird ou Idiot Bird)

Este é o combinador mais simples, a identidade. É uma função que retorna o seu próprio parâmetro. Em Haskell podemos escrever:

i x = x

Essa é justamente a definição da função id da biblioteca Prelude.

Combinador K (Kestrel)

Esse é o combinador constante, ele recebe dois argumentos e ignora o segundo. Em Haskell podemos escrever:

k x y = x

Essa é justamente a definição da função const da biblioteca Prelude.

Combinador S (Starling)

O combinador S é uma função que pode ser definida da seguinte maneira em Haskell:

s x y z = x z (y z)

Podemos escrever o combinador I usando apenas os combinadores S e K como

i = s k k

A demonstração é a seguinte:

Aplicando a função (s k k) sobre um argumento x temos

(s k k) x = s k k x

Substituindo s por sua definição,

(s k k x) = k x (k x)

Agora seja f = (k x) uma função qualquer. Temos que k x f = x por definição, então

k x (k x) = k x f = x = i x

É possível mostrar que qualquer combinador pode ser escrito como função dos combinadores S e K!

Combinador B (Bluebird)

O combinador B pode ser escrito em função de S e K como

b = s (k s) k

Vamos ver a cara dessa função aplicada a alguns argumentos. Seja f1 = (k s). Usando currying vemos que essa é uma função que recebe um argumento e sempre retorna s. Aplicando b sobre um argumento x, temos

b x = s f1 k x = f1 x (k x) = s (k x)

Seja f2 = (k x). Temos que f2 é uma função que recebe um argumento qualquer e sempre retorna x. Assim, temos b x = s f2. Vamos aplicá-la sobre mais dois parâmetros y e z:

(s f2) y z = s f2 y z = f2 z (y z) = x (y z)

Ou seja

b x y z = x (y z)

O que esta função faz é a composição das funções x e y e é justamente a definição do operador (.) do Haskell.

Combinador C (Cardinal)

Esse combinador pode ser escrito em função dos combinadores K, S e B como

c = s (b b s) (k k)

Pode-se mostrar que esse combinador equivale a

c f x y = f y x

Essa é justamente a definição do operador flip do Prelude, que retorna uma função com a ordem dos parâmetros trocada.

Combinador Y (Sage Bird)

Simplificadamente o combinator Y é um combinador que transforma uma função não recursiva em uma função recursiva!

Antes de descrevê-lo, vamos analisar como faríamos isso sem o combinador Y. Para isso, vamos usar como exemplo uma função que calcula o fatorial [8]. Fazendo uso de recursão, podemos escrever:

fat = \n -> if n == 0 then 1 else n * fat (n - 1)
fat 5 -- imprime 120

A expressão lambda acima não é um combinador porque depende de fat, que não foi passado como parâmetro.

Vamos tentar passar a função por parâmetro então

fat' = \f n -> if n == 0 then 1 else n * f (n - 1)

Observe que fat’ não é uma função recursiva. Agora fazemos o seguinte:

fat = fat' fat
fat 5 -- imprime 120

Estamos definindo fat como sendo o resultado de fat' quando passamos fat como parâmetro. Parece mágica, mas em linguagens com avaliação preguiçosa isso vai funcionar. Por quê?

Vamos analisar alguns exemplos. A função fat pode ser desmembrada em

fat = 
  fat' fat = 
      \n -> if n == 0 then 1 else return n * fat (n - 1)

Para n = 0, a função retornará 1 e não se dará ao trabalho de calcular fat(-1).

Vamos agora desmembrar a função duas vezes:

fat = 
  fat' fat = 
      fat' (fat' fat) = if n == 0 then 1 else return n * 
      (if (n - 1) == 0 then 1 ele return (n-1)* fat (n - 2))

Para n = 1, é possível ver que uma avaliação preguiçosa não precisa desmembramentos do que isso.

Ok, conseguimos transformar uma função não recursiva fat' em uma função recursiva. Vamos criar uma função auxiliar para que a função fat' seja passada como parâmetro

aux f = f (aux f)
fat = aux fat'
fat 5 -- imprime 120

A função aux faz o que queremos que o combinador Y faça: recebe uma função não recursiva (por exemplo fat') e retorna uma recursiva (nesse caso fat). Entretanto, aux não é um combinador porque o aux que é chamado recursivamente é uma variável livre.

Vamos apresentar então o combinador Y. Sua definição teórica é dada por

y = \f -> (\x -> f (x x)) (\x -> f (x x))

Ainda não consegui entender essa definição de maneira clara o suficiente para arriscar uma explicação aqui. Minha sugestão é ler [8], onde Mike Vaniel deriva a função acima em Scheme.

De qualquer maneira, se formos implementá-lo em Haskell, teremos um erro de compilação devido a tipo infinito [3]. Para resolver esse problema, [4] apresenta a seguinte solução:

newtype Rec a = In { out :: Rec a -> a }

y :: (a -> a) -> a
y = \f -> (\x -> f (out x x)) (In (\x -> f (out x x)))

Para evitar problemas com tipo infinito, encapsulamos o segundo termo (\x -> f (x x)) em um novo tipo, o Rec a, através do construtor In. As funções lambda recebem x que é do tipo Rec a e por isso não podemos aplicar x sobre x diretamente. Antes devemos “desencapsular” a função contida nele usando out.

Lembrando, o newtype define uma estrutura semelhante a um tipo de dado algébrico, com a diferença que só se permite um construtor, no caso definido pelo In, e um “getter”, especificado pelo out.

Por exemplo, vamos definir um tipo chamado NovoTipo que encapsula uma função que recebe um tipo genérico e retorna esse mesmo tipo genérico, ou seja, com tipo a -> a.

newtype NovoTipo a = Construtor { getter :: a -> a }

Podemos encapsular uma função que recebe e retorna um inteiro. Por exemplo:

somaUm::Int -> Int
somaUm v = v + 1
let x = Construtor somaUm

Podemos verificar o tipo dessa variável no ghci:

> :type x
x :: NovoTipo Int

O getter extrai a função que foi encapsulada, ou seja, podemos fazer:

> (getter x) 9
10

Outros combinadores

Este site [5] tem uma lista mais extensa de combinadores. Existe um pacote do Haskell chamado data-aviary [6] contendo a implementação de diversos desses combinadores.

Reg Braithwaite discute alguns combinadores na prática usando Ruby no seu blog/projeto homoiconic [7].

Conclusão

Fiquei satisfeito em desviar um pouco da leitura do livro Real World Haskell para aprender mais sobre combinadores. Graças a isso acabei descobrindo coisas interessantes como a origem do nome da empresa do Graham e que algumas funções muito usadas em Haskell são combinadores.

Além disso, embrora o combinador Y não seja tão útil na prática, as ideias por trás dele são muito interessantes (e, na minha opinião, complicadas!).

Referências

[1] Paul Graham – Biografia
[2] How to Mock a Mocking Bird – Smullyman
[3] StackOverflow – Y Combinator in Haskell
[4] [Haskell] How to define Y combinator in Haskell
[5] Combinator Birds
[6] Hackage – The data-aviary package
[7] Github – Homoiconic
[8] Mike’s World-O-Programming – The Y Combinator
[9] Y-Combinator em Javascript


Review – The Pragmatic Programmer: From Journeyman to Master

Julho 1, 2012

Faz algum tempo que comprei o livro The Pragmatic Programmer: From Journeyman to Master (há também a versão em português, dica do Leonardo) e recentemente terminei de lê-lo.

É um livro sobre desenvolvimento de software. Encontrei boas recomendações para ele em diversos sites e portanto decidi dar uma olhada. O livro está organizado em 8 capítulos que estão divididos em seções menores, num total de 46.

Cada uma dessas seções corresponde a uma dica relacionada a alguma das diversas fases do processo de desenvolvimento de sofware, incluindo levantamento de requisistos, projeto, desenvolvimento e teste. Vou comentar brevemente sobre os tópicos que achei mais interessantes.

A Teoria da janela quebrada. Uma janela quebrada em um prédio de uma cidade dá a impressão de abandono aos habitantes locais. Aí começam a aparecer pichações, danos estruturais começam a aparecer até que o senso de abando se torna evidente. Analogamente, uma janela quebrada no desenvolvimento de software são designs ou códigos ruins, que se forem deixados para consertar depois dão a sensação de descaso com a qualidade do software e fatalmente a qualidade do código se deteriorará.

Software bom o bastante. A dica pode ser resumida na frase “um ótimo software hoje é melhor do que um software perfeito amanhã”.

Seu portifólio de conhecimento. Os autores sugerem metas para construir e atualizar sua base de conhecimento:

  • aprenda uma linguagem todo ano
  • leia um livro técnico a cada trimestre
  • leia livros não-técnicos
  • assista aulas

Princípio DRY. Em inglês, DRY é um acrônimo Don’t Repeat Yourself. A motivação é que duplicação causa re-trabalho e devemos sempre manter as duplicações consistentes. Pra mim essa dica parecia óbvia, mas eu nunca tinha pensado nela conscientemente.

Um caso em que ela aparece é nos comentários de código. Um comentário que explica o código viola o princípio DRY porque o código em si já possui essa informação. De fato, já me deparei diversas vezes com código legado em que o comentário dizia uma coisa e o código fazia outra, provavelmente porque alguém alterou o código mas esqueceu de atualizar o comentário. Isso me lembra que uma citação:

Code never lies, comments sometimes do.

Ron Jeffries

O autor prega que código com bons nomes de variáveis e funções não exige comentário. Eu gosto de comentar pelo menos funções. Nesses casos, se estiver trabalhando com controle de versão, eu geralmente coloco o número da revisão em que em adicionei o comentário, pois se alguém quiser saber se o comentário corresponde ao código, basta fazer um diff. O uso ou não de comentários é um assunto controverso.

Balas traçantes. (Trace bullets) A analogia vem da situação em que você deseja acertar um alvo no escuro usando uma metralhadora. Uma abordagem é calcular precisamente a posição do alvo, a orientação da arma e atirar. Isso funciona bem quando o alvo é estático, mas em muitos casos o alvo é móvel e o tempo gasto calibrando o alvo pode ter sido em vão. Uma abordagem diferente é usar balas que deixam rastros luminosos definindo sua trajetória. Com isso o atirador tem um feedback que o ajuda a calibrar o próximo tiro.

No contexto de desenvolvimento de software, temos especificações que mudam com o tempo. Atirar com balas traçantes consiste em implementar uma arquitetura com o mínimo de funcionalidades, mas fazendo uso de todo o sistema desde a consultas simples a banco de dados até a interface gráfica. A vantagem deste método é que você consegue um feedback mais rápido do cliente, o que pode ajudar a consolidar as especificações.

Projeto por contrato. (Design by contract) O conceito de projeto por contrato foi desenvolvido por Bertrand Meyer para a linguagem de programação Eiffel. Basicamente, o contrato é caracterizado por pré-condições, pós-condições e invariantes em uma função. Pré-condições são hipóteses que a função supõe verdadeiras como por exemplo um parâmetro não nulo. As pós-condições são condições que devem ser satisfeitas após a execução do método. Finalmente, as invariantes são condições que valiam antes da chamada da função e continuam valendo depois, embora elas não necessariamente valessem ao longo da execução da função.

Contratos estão relacionados com testes e corretude de código. Recentemente o Google disponibilizou uma biblioteca chamada Cofoja (Contracts for Java) que permite o uso de contratos através de anotações.

Programação assertiva. Consiste no uso de assertivas para verificar condições que você supõe serem verdadeiras. A dica dada é se você acha que uma condição nunca ocorrerá em um determinado trecho de código, então adicione uma asserção para garantir que não irá de fato.

Eu particularmente gosto de assertivas, porque servem como forma de documentar o código e ajudam na depuração de erros, quando alguma hipótese foi violada.

Em Java as assertions são desabilitadas por padrão, pois o argumento é que elas podem impactar o desempenho do código se posta em produção. Eu acho que mesmo em produção assertivas podem ser usadas e se a verificação de alguma delas é tão complexa que pode interferir no tempo de execução do código, talvez valha a pena deixá-la como um teste.

Para garantir que assertivas estão ligadas em Java, podemos adicionar o seguinte trecho de código em alguma classe que será chamada durante a execução do código:

static {
  boolean assertsEnabled = false;
  // Linha não executada com assert desligado
  assert assertsEnabled = true; 
  if (!assertsEnabled)
    throw new RuntimeException("Asserts desabilitados!");
} 

Lei de Deméter. A lei de Deméter é um guia de desenvolvimento para diminuir o acoplamento entre partes de um software. No caso particular de métodos e funções, a lei diz que um método só deve chamar os métodos pertencendo: ao próprio objeto, a um objeto passado como parâmetro, a objetos instanciados dentro do método e a objetos declarados dentro do método.

O nome vem do projeto Demeter desenvolvido da Universidade Northeastern que é sobre programação adaptativa.

O nome do projeto faz referência à Deusa grega da agricultura, Deméter, pois os criadores do projeto estavam trabalhando em uma simplificação de uma linguagem chamada Zeus, queriam nomear a ferramenta com uma nome relacionado e optaram por Deméter, a irmã de Zeus [1].

Acoplamento temporal. Muitos designs buscam o baixo acoplamento estrutural, entre classes do projeto. Um termo que eu não conhecia, mas achei interessante, é o acoplamento temporal, que representa a dependência entre as ações executadas pelo software. Um baixo acoplamento temporal é desejável pois facilita o uso de programação paralela.

Conclusão

O livro contém diversas dicas e descrevi as que achei mais interessantes. O texto está bem escrito e é de leitura fácil. A divisão em pequenas seções facilita a leitura casual.

Outros tópicos cobertos sobre os quais não comentei são: refatoração, testes, levantamento de requisitos, escrita das especificações, uso de ferramentas de modelagem, etc. Pelo que pude perceber, a essência dos conselhos dados segue a linha da metodologia ágil.

Embora eu não esteja seguindo as metas sugeridas, eis aqui o status delas:

  • Livros técnicos: esse foi o primeiro que li no ano
  • Livros não-técnicos: faz muito tempo que não leio um :/
  • Nova linguagem: estou aprendendo Haskell desde o ano passado, mas ainda tenho vontade de aprender R e Erlang
  • Aulas: este ano fiz o curso de PGM! Pretendo me matricular no curso de Visão Computacional até o fim do ano.

Referências

[1] Demeter project: What is Demeter?


Leitura e escrita de dados em Haskell

Junho 17, 2012

Hoje vamos comentar sobre leitura e escrita de dados em Haskell. A minha principal referência é o Capítulo 7 do livro “Real World Haskell“.

Começamos com um exemplo bem simples que lê e escreve na saída padrão:

-- hello.hs
main = do
     putStrLn "Entre com seu nome:"
     inpStr <- getLine
     putStrLn ("Ola " ++ inpStr)

Para rodá-la podemos fazer :load hello.hs seguido de main no ghci ou runghc hello.hs no próprio terminal ou gerar um executável com ghc hello.hs.

Aqui já temos diversos conceitos novos. Primeiramente temos as funções de leitura e escrita, respectivamente getLine e putStrLn.

Vamos analisar a assinatura de tipos dessas funções:

putStrLn :: String -> IO ()
getLine :: IO String

Os tipos IO () e IO String representam uma ação de IO. Um jeito de interpretá-los é como sendo um invólucro (wrapper) para um tipo. Assim, IO String é um invólucro para o tipo String e IO () é um invólucro para um vazio.

Entrada e saída de dados são tarefas intrinsecamente não-puras, pois em uma chamada para leitura de dados (p.e. usando getLine) nem sempre retornará o mesmo resultado e uma escrita dos dados (p.e. usando putStrLn) possui efeitos colaterais externos.

Haskell sendo uma linguagem puramente funcional, faz uso de ações para poder lidar com tarefas não-puras. Futuramente, quando for falar sobre Mónades, pretendo explicar melhor sobre isso.

Seguindo com a interpretação do exemplo, temos o operador “<-“. Ele é uma maneira de executar uma ação, “extraindo” o seu conteúdo. Nesse caso, estamos “extraindo” a String lida com o getLine e atribuindo à variável inpStr.

Temos também o operador return, que encapsula um tipo em uma action, sendo o oposto do operador “<-” e não deve ser confundido com a palavra-chave de mesmo nome em linguagens imperativas.

O seguinte exemplo demonstra o encapsulamento de uma String em uma action, seguida da extração da mesma.

main = do
     inpStr <- return "Encapsulando e extraindo mensagem"
     putStrLn inpStr

O bloco definido pela palavra-chave do pode ser reescrito como uma sequência de operações ligadas por operadores do tipo “>>” e “>>=“. Analisando as assinaturas de tipos desses operadores temos:

(>>) :: (Monad m) => m a -> m b -> m b
(>>=) :: (Monad m) => m a -> (a -> m b) -> m b

Aqui Monad é uma typeclass. IO é uma instância de Monad. O operador “>>” serve para conectar duas ações, descartando o resultado da primeira e retornando a segunda.

main = 
     putStr "Ola" >> putStrLn " Mundo"

O operador “>>=” executa uma ação e alimenta uma função com o valor retornado. Essa função gera uma nova ação. Um exemplo:

main = 
     getLine >>= (\x -> putStrLn $ "Ola " ++ x)

Assim, o exemplo do início do post poderia ser reescrito sem o uso da palavra-chave do:

main =
     putStrLn "Entre com seu nome:" >> 
       getLine >>= 
         (\inpStr -> putStrLn $ "Ola " ++ inpStr)

Manipulação de arquivos de texto

A leitura e escrita de arquivos de texto em Haskell é bastante parecida com a de linguagens imperativas. Temos a função openFile

openFile :: FilePath -> IOMode -> IO Handle

que recebe o caminho do arquivo (uma string) e um modo de acesso (leitura, escrita, etc.)), e retorna um manipulador (handler) do arquivo (encapsulado em uma ação de IO).

Temos também as funções hGetLine que lê uma linha do manipulador, hPutStrLn que escreve uma linha no manipulador e hIsEOF que decide se o manipulador atingiu o final do arquivo.

import System.IO
import Data.Char(toUpper)

main :: IO()
main = do
     inh <- openFile "entrada.txt" ReadMode
     outh <- openFile "saida.txt" WriteMode     
     mainloop inh outh
     hClose inh
     hClose outh

mainloop inh outh = 
         do ineof <- hIsEOF inh
            if ineof
               then return()
               else do inpStr <- hGetLine inh
                       hPutStrLn outh (map toUpper inpStr)
                       mainloop inh outh

Temos os manipuladores para a entrada padrão, a saída padrão e a saída de erro. Desta forma poríamos usar as seguintes definições:

getLine = hGetLine stdin
putStrLn = hPutStrLn stdout
print = hPrint stdout

hGetContents

Ao invés de definir a função mainloop como no exemplo acima, podemos usar a função hGetContents

hGetContents :: Handle -> IO String

import System.IO
import Data.Char(toUpper)

main :: IO ()
main = do 
       inh <- openFile "entrada.txt" ReadMode
       outh <- openFile "saida.txt" WriteMode
       inpStr <- hGetContents inh
       hPutStr outh (map toUpper inpStr)
       hClose inh
       hClose outh

Como Haskell é uma linguagem com avaliação preguiçosa (lazy evaluation), no exemplo acima a função hGetContents não carregará todo o arquivo em memória, mas fará a leitura e escrita em partes.

readFile, writeFile e interact

As funções readFile e writeFile simplificam a leitura e escrita em arquivos de texto para casos como o do nosso exemplo, abstraindo o uso direto dos manipuladores:

readFile :: FilePath -> IO String
writeFile :: FilePath -> String -> IO ()

A nova versão fica bastante concisa:

import System.IO
import Data.Char(toUpper)

main :: IO ()
main = do 
       inpStr <- readFile "entrada.txt"
       writeFile "saida.txt" (map toUpper inpStr)

No caso particular de estarmos trabalhando com a entrada e saída padrão, podemos usar a função interact. Ela lê uma String da entrada padrão, processa com a função passada como parâmetro e retorna a String processada.

interact :: (String -> String) -> IO ()

Nesse caso nosso código se reduziria a uma linha!

import Data.Char(toUpper)

main :: IO ()
main =
       interact (map toUpper)

Buffering

Se rodarmos o código acima no ghci, ao mesmo tempo em que digitamos os caracteres eles passam a ser impressos no terminal. Há três tipos de buffers NoBuffering, LineBuffering e BlockBuffering. O modo padrão no ghci é o NoBuffering. Podemos mudar o modo padrão através da função hSetBuffering,

main :: IO ()
main = do
       hSetBuffering stdin LineBuffering
       interact (map toUpper)

Um efeito colateral indesejável é que depois que executarmos o main, os próximos comandos digitados no ghci só aparecerão depois de apertarmos o Enter :P

Parâmetros via linha de comando

Para obter os parâmetros passados como parâmetro via linha de comando, podemos usar a função getArgs presente em System.Environment

getArgs :: IO [String]

Ele retorna uma action contendo uma lista de strings correspondente aos parâmetros.

import System.IO
import System.Environment

main = do
     list <- getArgs
     putStrLn(show(list))

Praticando com problemas de programação

Agora que já aprendi o básico de leitura da entrada e saída padrão, vou começar a praticar no SPOJ-Br com os problemas da OBI. Até agora eu vinha usando o project Euler pois lá só precisa submeter a solução, não importando o modo como se faz a leitura/escrita dos dados.

Outra maneira de praticar é com a lista de 99 problemas de Haskell, mas esse eu ainda não tentei.

Minha sugestão de problemas para serem resolvidos no SPOJ-Br são Quadrados, Soma, Fatorial, Primo. Alguns com lógica bem simples também, mas um pouco mais chato de processar a entrada são Pneu, Fliperama, Garçom, Tacógrafo e Sedex.

Spoiler!

No problema Quadrados dá fazer uma solução bem compacta usando conceitos discutidos anteriormente como IO, funções lambda, currying e composição de função:

main = interact ((++ "\n") . show . (\x -> x*x) . read)

Referências

[1] Real World Haskell – Capítulo 7
[2] Haskell.org – Introduction to IO


Kindle

Março 4, 2012

Faz algum tempo já que comprei um Kindle da Amazon. Embora não leia com muita frequência, das vezes que precisei (longas esperas em aeroportos, dentro do avião), ele foi uma aquisição útil.

Nesse post vou comentar minhas impressões e registrar respostas para dúvidas que tive ao comprar o aparelho.

Vantagens

A principal vantagem de um leitor como o Kindle sobre tablets é o uso do e-ink. Por causa dessa tecnologia o Kindle não precisa de luz para exibir o texto, mas por outro lado é necessário luz ambiente para poder usá-lo, da mesma forma que um livro de papel. Isso permite um maior tempo de leitura sem que a vista fique cansada.

O aparelho possui uma película protetora fosca para reduzir o reflexo. Entretanto, ainda acho um pouco incômodo quando há uma fonte de luz muito próxima incidindo diretamente na tela, como por exemplo uma luminária de mesa ou mesmo as lâmpadas dos aviões.

Outra grande vantagem do uso do e-ink é a duração da bateria. Se o wi-fi estiver desabilitado, um Kindle pode ficar mais de um mês sem recarregar.

Formatos aceitos

O Kindle possui um formato próprio, o .azw, enquanto o formato padrão dos livros não comprados pela Amazon é o .mobi, que também é aceito, além do .txt.

Ele aceita arquivos .pdf também, mas minha experiência não é muito boa com a versão de 6 polegadas. Como o Kindle não reconhece o texto do pdf, o melhor que ele pode fazer é diminuir a escala para a página caber na pequena tela. Outra possibilidade é manter o tamanho original, mas aí provavelmente será preciso scroll horizontal (mesmo visualizando no modo paisagem), o que dificulta o fluxo de leitura. Talvez com a versão DX, de 9.7 polegadas em modo paisagem esse problema seja evitado.

Rede Wi-fi

O Kindle usa wi-fi para baixar os livros armazenados na Amazon. Existe um navegador embutido onde é possível acessar alguns sites como Wikipedia e também fazer compras diretamente pelo site da Amazon. Existem versões que usam 3G.

É possível ainda fazer a transferência de arquivos por um cabo USB.

Funcionalidades

Além do navegador, existem algumas funcionalidades do software embutido no Kindle:

* Dicionário – procura o significado de uma palavra no dicionário simplesmente posicionando o cursor sobre a mesma (o dicionário inglês já vem instalado, mas é possível instalar dicionários em outras línguas);

* Conversor de texto para voz: funciona com quase qualquer formato no qual o Kindle seja capaz de reconhecer as palavras (por exemplo .txt, mas não .pdf). O som fica “robotizado”, mas não dá pra fazer milagre com um conversos automático;

Livros

Existem muitos títulos disponíveis gratuitamente na internet, principalmente aqueles mais antigos. Outra possibilidade é baixar textos na web longos demais para se ler em um computador. Alguns aplicativos como o instapaper fazem um trabalho razoável em converter em formato aceito pelo Kindle.

Adquirir livros pela Amazon é bem simples. A principal dificuldade para estrangeiros é a necessidade de um cartão internacional. Descobri que a Amazon também aceita cartões pré-pagos internacionais, como o travel money, que é uma opção mais segura.

Os livros comprados ficam armazenados na nuvem associados à sua conta, então você pode trocar de aparelho ou ler os livros no aplicativo Kindle para computador sem ter que adquirir novas cópias.

Leitura adicional

[1] Garota sem fio – Review Kindle


Eloquent Javascript – Parte 2

Fevereiro 20, 2012

Esse post é uma continuação do meu estudo do livro Eloquent Javascript, iniciado aqui. Agora eu também mantenho uma página com o índice de livros com todos os posts referentes a eles.

Capítulo 6 – Programação Funcional

Esse capítulo apresenta algumas funcionalidades comuns a linguagens funcionais como funções anônimas, map, reduce, aplicação parcial de função e composição de funções. Boa parte do capítulo é dedicada a apresentar uma solução para um problema de converter texto em HTML. Achei interessante mostrar a aplicação de programação funcional num exemplo real, mas achei que fugiu um pouco do tópico.

Achei estranho o fato de o autor não mencionar closures, que é uma técnica muito empregada em javascript e que a meu ver é base para várias dessas funcionalidades de programação funcional apresentadas.

Em javascript podemos ter funções dentro de funções. Nesse caso, as funções internas têm acesso às variáveis locais definidas nas funções mais externas. No código abaixo, a função interna (anônima) tem acesso (leitura e escrita) à x e também à variável y.

Em geral, quando a função termina, perdemos a referência para as variáveis locais definidas dentro dessa função. Em javascript, funções podem ter comportamento parecido com o de objetos. Uma prova disso é que podemos retornar uma função, como no exemplo abaixo.

Combinado com a propridade de acessar membros das funções externas, temos um mecanismo de acesso indireto aos membros internos da funções. Esse é o conceito mais básico de closures.

function closure(y){
    var x = 10;
    return function(z){
        alert(x + y + z);
        x++;
    }
}
var foo = closure(5);
foo(3); // imprime 18
foo(3); // imprime 19

Com closure podemos fazer a aplicação parcial de funções. O segredo é que a keyword arguments contém todos os argumentos passados para uma função, mesmo que eles não sejam usados pela função. Assim, a função partial definida abaixo recebe uma função func como argumento. Todos os parâmetros adicionais, ele repassa para func, junto com os parâmetros que serão passados para a função anônima quando ela for invocada.

A função asArray serve apenas para converter parameters para um array, pois é esse o tipo esperado pela função apply.

// É preciso converter a lista de argumentos para array
// antes de passar para a função apply
function asArray(quasiArray, start){
    var result = [ ];
    for(var i = (start || 0); i < quasiArray.length; i++)
        result.push(quasiArray[i]);
    return result;
}

// Partial salva os primeiros argumentos da função em 'fixedArgs'
// Quando a função for de fato chamada, apenas os argumentos restantes
// precisam ser passados
function partial(func){
    // O primeiro argumento é func.
    var fixedArgs = asArray(arguments, 1); 
    return function(){
        return func.apply(null, fixedArgs.concat(asArray(arguments)));
    };
}

Para ficar mais claro, considere uma função anônima implementando uma soma e suponha que passamos esta função para partial, mais o parâmetro 3. Este parâmetro será aplicado à soma antes dos parâmetros passados para a função “objeto” foo.

var foo = partial(function(a, b){ return a + b; }, 3);
var res = foo(20); // 23

Usando uma ideia parecida, podemos fazer a composição de duas funções. No exemplo abaixo, se passarmos uma função foo() e bar() para composition, obteremos uma função equivalente a foo(bar()):

// Composição de duas funções
function composition(func1, func2){
    return function(){
        return func1(func2.apply(null, asArray(arguments)));
    };
}

Um exemplo de uso dessa função é a composição de uma função que retorna o dobro do valor da entrada e outra que soma dois elementos.

var composed = composition(
    function(v){ 
        return 2*v;
    }, 
    function(a, b){
        return a + b;
    }
)
alert(composed(6, 7)); // 26

Capítulo 7 – Busca

Logo no começo do capítulo o autor observa que este não introduzirá nenhuma funcionalidade nova de Javascript. De fato, é apresentado o problema do menor caminho, o qual é atacado inicialmente através de um algoritmo aleatório força bruta. Depois ele descreve rapidamente o algoritmo A* e apresenta uma solução em Javascript, usando alguns dos conceitos ensinados até então.

Capítulo 8 – Orientação a Objetos

Neste capítulo são apresentadas técnicas para trabalhar com orientação a objetos em Javascript. Basicamente, podemos criar um objeto a partir de uma função com o operador new.

function Foo(){
}
var obj = new Foo();

Pensando em classes, Foo age como construtor e definidor da classe. Poderíamos declarar membros e métodos dentro desse construtor.

function Foo(int x){
    this.x = x;
    this.bar = function(){
        alert(this.x);
    }
}
var a = new Foo(10);
a.bar();
var b = new Foo(11);
b.bar();

É possível adicionar e modificar métodos da “classe” depois que ela já foi definida através dos chamados protótipos (prototype). Toda função tem uma propriedade chamada prototype e este herda um conjunto de propriedades do protótipo de Object. Entre essas propriedades estão toString e constructor. O constructor aponta para a própria função, de forma que as duas linhas abaixo são equivalentes:

var a = new Foo(10);
var a = new Foo.prototype.constructor(10);

O autor continua apresentando técnicas para se trabalha com OO em Javascript através de um exemplo bem interessante de simulação, consistindo de um mapa 2D e várias criaturas ficam se movimentando. É possível ver a simulação em tempo real (em modo texto) com o interpretador embutido na página.

Ao apresentar o código dessa simulação, ele descreve uma situação problemática: passar um método como parâmetro para uma função externa (callback), como no exemplo abaixo:

function say(func){
    alert(func());
}

function Foo(){
    this.v = [10];
    this.get = function(){
        return this.v;
    };
    this.bar = function(){
        say(this.get);
    };
}
var c = new Foo();
c.bar();

A função foo irá imprimir undefined. Segundo [3], ao executar a função say, a referência ao objeto é perdida. Para contornar a situação, é possível usar um closure, para manter a referência ao objeto.

Nesse sentido, é comum definir a seguinte função:

function bind(func, obj){
    return function(){
        return func.apply(obj, arguments);
    }
}

A função retornada por bind contém a referência ao objeto obj. Podemos reescrever o método bar da seguinte maneira:

this.bar = function(){
    say(bind(this.get, this));
}

Herança

Um mecanismo de herança pode ser obtido em Javascript através de protótipos. A ideia é clonar o protótipo da classe base.

function clone(object) {
    function Constructor(){};
    Constructor.prototype = object;
    return new Constructor();
}

Object.prototype.inherit = function(baseConstructor) {
  this.prototype = clone(baseConstructor.prototype);
  this.prototype.constructor = this;
};

Um exemplo de uso:

function Base(){ } 
Base.prototype.x = 10;
Base.prototype.bar = function(){
    alert(this.x);
}
function Derived(){ }
Derived.inherit(Base);

Capítulo 9 – Modularidade

Este capítulo não apresenta nenhuma novidade de Javascript, focando mais em boas práticas sobre organização do código. São ensinados truques para gerenciar dependências entre módulos e maneiras de se publicar somente um subconjunto das funções presentes no módulo.

Capítulos 10, 11, 12, 13 e 14

O capítulo 10 apresenta expressões regulares em Javascript. Os capítulos restantes falam sobre o básico programação Web, DOM (Document-Object Model), eventos dos browsers e requisições HTTP.

Como eu já conhecia o básico de cada assuntos desses e no livro não foram muito aprofundados, não tenho nenhum comentário adicional para fazer aqui.

Conclusão

Javascript é mais complicado do que parece! Ainda tenho bastante dúvida sobre closure e protótipos e pretendo pesquisar mais sobre o assunto.

Leitura adicional

[1] Stackoverflow – How do JavaScript closures work?
[2] Javascript Closures
[3] Stackoverflow – JavaScript Callback Scope


Eloquent JavaScript – Parte 1

Janeiro 29, 2012

Já programei em Javascript diversas vezes, mas nunca me disciplinei a aprender a linguagem direito. Sempre seguia tutoriais para descobrir o jeito mais direto de fazer o que eu queria.

Recentemente voltei a programar em Javascript e desta vez decidi ler livros introdutórios sobre essa linguagem. Comecei com o Eloquent Javascript, que está disponível gratuitamente online.

Capítulos 1 e 2

Esses dois capítulos apresentam o básico de programação e a sintaxe básica de Javascript. Passei rapidamente por esses dois capítulos. Uma coisa que eu não lembrava (ou não sabia :P) era o seguinte:

Temos 6 tipos em Javascript: number, string, boolean, object, function e undefined.

O interessante é que segundo a especificação do Javascript (ecma) number é implementado como um double 64 bits (padrão IEEE 754), ou seja, 1 bit pra sinal, 11 bits para expoente e 52 bits de precisão. Isso significa que podemos representar inteiros entre -2^{52} - 1 a 2^{52} -1.

Capítulo 3 – Funções

Neste capítulo comenta-se sobre funções em programação geral e em Javascript. Há duas principais novidades que aprendi aqui.

Quando o número de parâmetros passados a uma função é menor que o número de parâmetros exigidos pela mesma, os parâmetros faltantes são completados com undefined. Por outro lado, se o número de parâmetros passados for maior, os parâmetros excedentes são descartados.

A outra coisa é que apenas funções definem escopo. Ao contrário de linguagens como C++, blocos ou laços não definem escopo. Assim, considere o exemplo abaixo:

var a = 2;
for(var i = 0; i <= 10; i++){
    var a = i;
}
// 'a' vale 10

As variáveis a no código acima se referem à mesma coisa. Agora considere o mesmo exemplo, só que com uma função:

function f(){
    var a = 10; 
}
var a = 2;
f();
// 'a' vale 2

Neste exemplo a variável a tem escopo local em f.

Capítulo 4 – Estruturas de dados: Objetos e Arrays

Uma das principais estruturas em Javascript é o Object. Ele é basicamente um array associativo (também conhecido como dicionário), ou seja, um array onde podemos usar strings como índices e elementos podem ser adicionados dinamicamente.

A sintaxe básica para a instanciação de um objeto é

var obj = new Object();
// ou simplesmente...
var obj = {};

Para inserir uma chave no objeto, podemos usar alguma das seguintes alternativas:

obj["chave"] = valor;
// ou...
obj.chave = valor;
// ou...
var v = "chave";
obj[v] = valor;

Se a chave não existia, ela é criada. Se já existia, seu valor é substituído. Para acessar o valor é da mesma forma, só que se tentarmos acessar o valor de uma chave que não existe, retorna-se undefined.

Para deletar uma chave do dicionário:

delete obj["chave"];
// ou...
delete obj.chave;
// ou...
var v = "chave";
delete obj[v];

Para testar se uma chave está definida no dicionário, usa-se a keyword in:

key in obj; // retorna um boolean

Outra estrutura útil é o Array, que pode ser instanciado como

var arr = new Array();
// ou simplesmente...
var arr = [];

Essa estrutura funciona com um mecanismo parecido com o dicionário, com a principal diferença de que as chaves devem ser números naturais e ao criar uma chave de valor N, todos os índices de 0 a N que ainda não existem são criados também.

Exemplo,

var arr = [];
arr[0] = 2; // arr = [2]
arr[4] = "oi"; // arr = [2, undefined, undefined, undefined, "oi"] 

Capítulo 5 – Tratamento de erros

Javascript possui um mecanismo de tratamento de exceção, com sintaxe try-catch-finally parecida com a de Java:

function foo(){
    throw Error("mensagem de erro");
}

try {
    foo();
}
catch (erro) {
    alert(erro.message); // Imprime "mensagem de erro"
}
finally {
    // Trecho de código A
}
// Trecho de código B

Basicamente qualquer objeto pode ser repassado para a keyword throw e nesse caso o bloco do catch será chamado. No exemplo, a variável erro apontará para o objeto Error, que é pré-definido e possui o campo message.

O bloco do finally é chamado no caso em que ocorre uma interrupção prematura do bloco try ou do catch (via return ou outro throw, por exemplo), representando um fluxo de código alternativo ao trecho B, que provavelmente só deve ser executado na ausência de erros.

Uma dúvida que me surgiu é como tratar diferentes tipos de exceções. Em Java é possível utilizar múltiplos blocos de catch. Já em Javascript, você deve tratar explicitamente através de if e else‘s. Uma alternativa nesse caso é adicionar no objeto um campo errorType contendo uma string, por exemplo [3].

Referências

[1] Dynamically creating keys in javascript associative array
[2] W3 Schools – JavaScript Array Object
[3] Handling specific errors in JavaScript