Smalltalk x Java

Chuck Norris x Bruce LeeNão é nenhuma novidade que eu programo em Java, amo Java e blá blá blá, o que não impede que eu escreva posts controversos quanto a isso. Então, este texto definitivamente não é bom para aqueles que gostam de ler que o Java é a melhor linguagem do mundo e etc. Por isso, já vou avisando: se você sofre de algum problema estomacal que surge quando alguém aponta defeitos no Java, este post definitivamente não é para você.

Agora, se você gosta de Java, conhece alguma coisa de Smalltalk ou pode enriquecer este post com comentários pertinentes e inteligentes, sua contribuição é mais do que bem-vinda!

Tentarei mostrar aqui a implementação de alguns algoritmos e trechos de código em Java e Smalltalk, para fins de comparação e julgando qual das alternativas eu achei mais interessante.

UPDATE: devido ao excelente feedback que obtive neste post, eu o atualizei com mais algumas comparações entre Java e Smalltalk.

Problema: cálculo de fatorial

Em Java

Java não fornece nenhum método para cálculo de fatorial, sendo então necessário definir um método para tal. O método pode ser implementado tanto usando um a abordagem iterativa quanto recursiva.

Fatorial - iterativo:

  1.  
  2. public static long factorialNonRecursive(long n) {
  3.     long v = n, i = n;
  4.     while (i-\- > 1) {
  5.         v *= i;
  6.     }
  7.     return (n < 0) ? 0 : (n == 0) ? 1 : v;
  8. }
  9.  

Fatorial - recursivo:

  1.  
  2. public static long factorialRecursive(long n) {
  3.     if (n < 0) return 0;
  4.     if (n == 0) return 1;
  5.     return n * factorialRecursive(n-1);
  6. }
  7.  

Como ninguém costuma implementar um método para cálculo de fatorial utilizando o método iterativo, então vamos considerar o método recursivo na comparação.

Em Smalltalk

Não é necessário definir um método para fazer o cálculo. No Smalltalk, tudo é um objeto, o que fica claro no código abaixo:

  1.  
  2. 5 factorial
  3.  

Neste exemplo, enviamos a mensagem factorial para o objeto 5, que é uma instância de SmallInteger, classe esta que estende Integer (que é quem define a mensagem factorial).

Não fique espantado, pois o código acima é tudo o que você precisa fazer para obter uma instância de Integer = 120. Nada mais.

Mas, supondo que Smalltalk não fornecesse tal funcionalidade pronta, como este método poderia ser escrito? (ver comentário de Carlos Heuberger, logo abaixo na página)

  1.  
  2. factorial: aNumber
  3.     aNumber = 0 ifTrue: [^ 1].
  4.     aNumber > 0 ifTrue: [^ aNumber * (self factorial: aNumber - 1)].
  5.     ^ 0.
  6.  

Neste código, definimos um método factorial que recebe um número como parâmetro. Veja que a característica dinâmica da linguagem torna a declaração do método e uso de variáveis muito simples. O que vale frizar é que, embora Smalltalk seja uma linguagem dinâmica, ela possui tipagem forte. Por isso, utilizar um objeto como sendo outro gera erros.

Voltando à explicação, utilizamos a mensagem ifTrue no objeto Boolean resultante da expressão (aNumber = 0), fazendo com que o bloco seja executado caso a condição seja verdadeira. Neste caso, retornamos o Integer 1 (através do caractere ^). De modo análogo, chamamos o método recursivamente caso o número for maior que zero, retornando o resultado do fatorial. Caso o número seja negativo, o método retorna 0. Perceba também o uso do caractere . (ponto), usado para demarcar o fim de uma instrução.

O interessante neste algoritmo é que tudo se resume à troca de mensagens entre objetos, e não ao uso de comandos da linguagem (como no caso do Java). Bastante intuitivo.

Vencedor

Considerando que existe uma mensagem prontinha para calcular fatoriais (e outras várias mensagens para cálculos matemáticos dos mais diversos), o Smalltalk leva a melhor.

Aliás, mesmo se desconsiderarmos o fato de que Smalltalk fornece tal método pronto, eu acho a versão do algoritmo em Smalltalk mais fácil de se entender, principalmente porque não é necessário que os programadores decorem sintaxes particulares à diversos tipos de construções da linguagem (if, for, while, do e switch como exemplos da linguagem Java). Basta saber enviar mensagens aos objetos (e saber utilizar o básico do Class Browser oferecido pelo ambiente Smalltalk).

Reforçando…

O Smalltalk fornece um número bastante reduzido de construções, já que o objetivo da linguagem foi literalmente imitar o mundo real. Como assim? Bom, todo processamento ocorre através da troca de mensagens entre objetos, seguindo um padrão <receiver> message <arg>. Por exemplo:

  1.  
  2. album play.
  3. album playFromTrack: 1.
  4. album playFromTrack: 1 to: 6.
  5.  

O código mostrado na listagem anterior soa bem mais natural (ou menos “computacional”) do que o que vemos em Java, não?! Bom, pelo menos para mim, soa!

  1.  
  2. album.play(1, 6);
  3.  

Se você não conhece Smalltalk (assim como eu não conhecia, a alguns dias atrás), tudo ficará mais claro nos próximos exemplos.

Problema: imprimir os números de 1 a 10

Em Java

A forma mais simples de se implementar isso é através da utilização de um loop for:

  1.  
  2. for (int i = 1; i <= 10; i++) {
  3.     System.out.println(i);
  4. }
  5.  

Em Smalltalk

Veja a grande diferença:

  1.  
  2. 1 to: 10 do: [ :elem |
  3.         Transcript show: elem; cr.
  4.     ].
  5.  

Em ambas versões, o código poderia ter sido colocado em uma única linha, mas por questões de legibilidade, preferi dividir.

Um objeto Number possui um método to: <stop> do: <aBlock>. O parâmetro stop seria um outro objeto Number que indica o final da iteração e o parâmetro aBlock recebe um bloco de código (ou closure) que deve ser chamado a cada iteração. A cada iteração, o método passa o elemento sendo iterado para a closure, que é recebido através do objeto elem.

Continuando com a explicação, a linha de código dentro da closure é igualmente interessante, onde enviamos a mensagem show para o objeto Transcript, para que o valor seja impresso no terminal, seguido de uma quebra de linha. O trecho de código abaixo se comporta da mesma forma:

  1.  
  2. Transcript show: elem.
  3. Transcript cr.
  4.  

Isso nos leva a crer que o caractere ; é usado quando se deseja enviar várias mensagens a um mesmo objeto. O cr é uma mensagem enviada a Transcript que faz com que uma quebra de linha (carriage return) seja impressa.

Vencedor

Na minha opinião, o trecho escrito em Smalltalk é mais intuitivo que o trecho escrito em Java. Talvez, para programadores C e C++, o trecho escrito em Java seja mais intuitivo do que o trecho em Smalltalk, pelo fato de que Java, C e C++ são muito parecidas sintaticamente, pelo menos em relação ao código em comparação aqui.

Problema: trabalhando com Collections

Quem trabalha constantemente com Collections em Java, deve odiar. Eu particularmente, odeio, pois as assinaturas de métodos e construtores foram cuidadosamente projetados para não serem práticos. :P

Em Java

Abaixo, um código para adicionar elementos em uma Collection e imprimí-los no terminal:

  1.  
  2. List<String> l = new LinkedList<String>();
  3. l.add("Trabalhar ");
  4. l.add("com Collections ");
  5. l.add("em Java ");
  6. l.add("é chato pra ");
  7. l.add("cacete.");
  8. for (String str : l ) {
  9.     System.out.println(str);
  10. }
  11.  

Se você ainda não deu um tiro na própria cabeça, já é alguma coisa. Gostaria de deixar claro que eu adoro Java, mas convenhamos: isso é podre.

Em Smalltalk

Abaixo, o mesmo código portado para Smalltalk (quer dizer, quase o mesmo, visto que mudei as Strings que são adicionadas à Collection):

  1.  
  2. |c|
  3. c := OrderedCollection with: ‘Trabalhar ‘ with: ‘com Collections ‘ with: ‘em Smalltalk ‘ with: ‘é bem melhor’.
  4. c do: [:str |
  5.     Transcript cr; show: str ].
  6.  

Neste programa utilizamos a classe OrderedCollection para armazendar os elementos. Para criar o objeto já com os objetos, enviamos a mensagem with:with:with:with: com os objetos que queremos adicionar à Collection.

A classe OrderedCollection é utilizada quando se deseja trabalhar com arrays de tamanho variável. Uma coisa interessante de ser dita (que costuma confundir a cabeça de novatos, como eu): o “Ordered” no nome não indica que os elementos dentro da Collection serão classificados; um array ordenado é diferente de um array classificado (conceito este que também é utilizado na API de Collections do Java, mais informações aqui).

Ok, uma vez que a Collection foi populada, utilizamos a mensagem do para mostrar os elementos da Collection no terminal, um a um.

Vencedor

Neste caso, é óbvio que eu prefiro o Smalltalk. E nem pensei duas vezes antes de escrever isto. :P

Problema: mostrar os pares e ímpares entre 1 e 10

Ver comentário do Icaro, logo abaixo na página.

Em Java:

  1.  
  2. for (int i = 1; i <= 10; i++) {
  3.     if (i % 2 == 0) {
  4.         System.out.println(i + " is even");
  5.     }
  6.     else {
  7.         System.out.println(i + " is odd");
  8.     }
  9. }
  10.  

Nenhuma novidade para quem conhece o básico da sintaxe da linguagem Java.

Agora, a versão Smalltalk:

  1.  
  2. 1 to: 10 do: [ :n |
  3.     n even ifTrue: [
  4.         Transcript show: n; show: ‘ is even’; cr]
  5.     ifFalse: [
  6.         Transcript show: n; show: ‘ is odd’; cr] ]
  7.  

Novamente pergunto: qual das versões soa mais “natural”? Na minha opinião, a versão Smalltalk. Neste código, iteramos entre os números 1 e 10. Para cada número, enviamos a mensagem even para verificar se o mesmo é par (even). Se sim, mostramos a mensagem 'x is even' (x é par) e, caso contrário, mostramos a mensagem 'x is odd' (x é ímpar).

Ah mais as diferenças entre a versão Java e a versão Smalltalk são mínimas.

Talvez sim… o que eu quis mostrar com este exemplo é que a linguagem Java fornece diversos comandos (para iterar, comparar etc, cada qual com sua estrutura sintática) que, no Smalltalk, são simples mensagens enviadas a objetos capazes de tratá-las. Mais detalhes podem ser vistos neste documento.

Vencedor

Na minha opinião, Smalltalk.

Problema: invocar um método via Reflection

Vamos supor a existência de uma classe chamada MyClass e, via reflexão, precisamos invocar o método showMessage().

Em Java

  1.  
  2. MyClass obj = new MyClass();
  3. Class clazz = obj.getClass();
  4. Method method = null;
  5.  
  6. try {
  7.     method = clazz.getMethod("showMessage", null);
  8.     method.invoke(obj, null);
  9. }
  10. catch (Exception exc) {}
  11.  

A API de reflexão do Java é muito boa, adoro ela. Se não fosse por ela, provavelmente ainda estaríamos programando aplicativos para a web utilizando Servlets e JSPs. Entretanto, veja que, para casos simples como este, ela costuma ser chata de se trabalhar; várias exceções precisam ser tratadas e o código em si é bastante “verboso”.

Em Smalltalk

Pá-pum:

  1.  
  2. myObj perform: #message
  3.  

Aqui estamos utilizando a mensagem perform:, que serve para requisitarmos o processamento de um seletor representado pelo parâmetro. Aqui, ao contrário do Java, não precisei agir defensivamente com blocos catch; se a mensagem em questão não existir, o objeto recebe uma notificação doesNotUnderstand:.

Vencedor

Nem vou responder.

Conclusão

Claro que aqui estamos apenas comparando as partes “sintaxe e API” do Java com a parte “sintaxe e API” do Smalltalk. Se entrarmos no mérito de discutir a comparação da parte “plataforma” do Java com a parte “ambiente” do Smalltalk, creio que a coisa fica ainda pior para o lado do Java. :)

Mas enfim, comparar a sintaxe de uma linguagem que conhecemos com uma que desejamos aprender é, na minha opinião, o melhor caminho a seguir para facilitar o aprendizado (e claro, caso as diferenças entre tais linguagens não sejam tão grandes a ponto de impossibilitar esta abordagem).

Tags: , , , , ,