Técnico

Scala – Primeiros Passos

Aprendendo Scala



Estou começando uma série de posts sobre Scala para divulgar as minhas experências com a linguagem e suas aplicações. Vou tentar passar o funcionamento da linguagem e seus recursos, assim como também um pouco do paradigma de programação funcional, e tentar chegar em aplicações práticas no ambiente de desenvolvimento.

Existem alguns tutoriais disponíveis na internet bem interessantes mas que as vezes jogam muito conteúdo sem dar tempo para assimilar os conceitos. Nessa série de posts tentarei colocar os conceitos e ferramentas de Scala e linguagens funcionais de uma maneira natural, conforme eu mesmo for evoluindo na linguagem e também dando exemplos e exercícios para que você possa assimilar o que está sendo passado. Vou tentar explicar também conceitos mais teóricos do paradigma funcional, para que a linguagem que estamos falando seja a mesma.

Minha experiência com Scala é algo bem próximo do zero, por isso estou escrevendo esta série de posts com o intuito de me forçar a estudar a linguagem e também ajudar a divulgar uma das possíveis alternativas ao Java, afinal se trata de uma linguagem nova e que ainda não foi muito difundida na comunidade. Tratarei bem do básico nos primeiros posts com exemplos bem trivias apenas para ajudar na compreensão dos conceitos básicos. Também colocarei algumas bibliografias de onde se pode encontrar documentação sobre a linguagem.

Linguagens funcionais e Scala



Não é nova a ideia de se programar em um paradigma funcional. Linguagens funcionais existem a quanse tanto tempo quanto existe linguagens de programação. Com certeza na sua faculdade você viu, ou verá, linguagens como LISP, Scheme, Erlang, Clojure, etc… e atualmente que está ficando bem conhecida: Scala que roda em cima da JVM, a virtual machine do Java. Linguagens funcionais mudam o paradigma para focar em ‘funções’. Assim como em linguagens orientadas a objeto onde a unidade básica é uma intância de um objeto, em linguagens funcionais a unidade básica é uma função. Uma função nada mais é do que uma operação que recebe parâmetros, executa alguma ação e devolve um resultado. O funcionamento é muito parecido com o dos métodos em linguagens orientadas a objeto.

O interessante sobre Scala, é que a linguagem permite uma mescla do paradigma funcional com orientação a objeto. Existem conceitos de ambos implementados na linguagem e que fazem sentido quando aplicados em conjunto. Por exemplo, em scala existe o conceito de classe e de instâncias de classes, que são os objetos. Também existem funções que independem de uma classe para existirem. Se trata de um conceito muito poderoso, pois possibilita toda padronização e controle do design do sistema que as linguagens orientadas a objeto permitem, sem restringir o uso do dinamismo e abstração da linguagem funcional.

Mas claro que Scala e linguagens funcionais tem seus problemas. Por exemplo, não existe uma IDE sólida como o Eclipse ou o NetBeans para Scala. O que dificulta no momento de trabalhar com projetos muito grandes, com uma equipe com vários programadores. O dinamismo da linguagem também impede uma análise de código que por exemplo o PMD, ou o FindBugs disponibilizam no Java. Dessa forma, deveríamos garantir que as boas práticas e o design está sendo seguido com nossos testes unitários.

Chega de filosofar sobre a linguagem, vamos para a parte prática.


Instalando Scala



Para instalar o Scala basta acessar a página oficial de download neste link, e fazer o download de acordo com sua plataforma. Feito isso, descompacte o conteúdo em alguma pasta na sua máquina e adicione a pasta ‘bin‘ ao path do seu sistema operacional. Para se certificar que foi tudo instalado corretamente, abra um console e digite ‘scala‘, o terminal do scala deve abrir e aparecer uma tela como essa:

Agora que o terminal está sendo executado, digite ‘1 + 1‘ e repare na saída:

[cc_javascript]
res0: Int = 2
[/cc_javascript]

É uma saída um tanto quanto confusa, mas que faz sentido no contexto do Scala. O que está acontecendo é que a unidade básica em Scala é uma função, portanto a operação + também é uma função, que foi executada no objeto ‘1‘, passando o parâmetro ‘1‘. Para se ter uma idéia melhor desse funcionamento, digite o seguinte no terminal:

[cc_javascript]
scala> 1.+(1)
[/cc_javascript]

e aperte Enter. Repare na saída:

[cc_javascript]
res1: Double = 2.0
[/cc_javascript]

Com exceção do tipo que era int e agora é Double, o funcionamento é exatamente o mesmo. Tal sintaxe já deve estar bem claro na cabeça da maioria dos programadores, mas a primeira sintaxe é só uma maneira difetente de se fazer a mesma chamada.

Conceitos básicos da linguagem



Vamos definir uma função para entender um pouco mais da sintaxe. Digite o seguinte:

[cc_javascript]
scala> def a = 2 + 2
a: Int
[/cc_javascript]

Aqui estamos definindo uma função com o nome ‘a‘ que não recebe parâmetros, e devolve o resultado da soma ‘2 + 2‘. A palavra restrita return não é necessária, o compilador consegue inferir que a última expressão da função será o valor devolvido. Repare também que não precisamos definir o tipo que a função devolve, o qual também foi inferido pelo compilador. Portanto ao contrário do que possa parecer, Scala é uma linguagem tipada em tempo de compilação, portanto o seguinte trecho não compila:

[cc_javascript]
scala> var b = “abc”
b: java.lang.String = abc

scala> b = a
:7: error: type mismatch;
found : Int
required: java.lang.String
b = a
^
[/cc_javascript]

Repare que o compilador inferiu que o tipo da variável ‘b‘ é String (note que é o String do java), e que o tipo devolvido por ‘a‘ é Int, logo o código não compila. De certa forma esse comportamento é bom, pois garante uma maior integridade do código, sem que você tenha que programar para “deixar o compilador feliz“, ou seja, não existem tantas regras na escrita do código, porque muitas podem ser deduzidas a partir do contexto.

Um outro conceito de Scala é a diferença entre chamada por valor, e chamada por nome. Na chamada por valor é feita a evaluação da expressão assim que o código é executado, mesmo que não seja necessário usar o resultado da chamada. A vantagem é que evitamos executar outras vezes a expressão para obter o resultado, pois este já foi determinado. Na chamada por nome a evaluação da expressão ocorre apenas quando o resultado da chamada será usado, tendo um comportamento mais lazy. A desvantagem é a execução da expressão várias vezes para se obter o mesmo resultado, mas a vantagem é não fazer a execução quando não for necessário. Vamos ver um exemplo da diferença entre a chamada por valor e por nome. Volte no terminal e defina a seguinte função:

[cc_javascript]
scala> def loop: Int = loop
loop: Int
[/cc_javascript]

Agora defina uma função que recebe dois inteiros e devolve o primeiro:

[cc_javascript]
scala> def primeiro(x: Int, y: Int) = x
primeiro: (x: Int,y: Int)Int
[/cc_javascript]

Agora execute a chamada passando ‘loop‘ como segundo parâmetro e se prepare porque seu console irá travar na execução =):

[cc_javascript]
scala> primeiro(0, loop)
_
[/cc_javascript]

Vamos fazer uma pequena modificação na definição de ‘primeiro‘ para tornar possível a execução de tal código:

[cc_javascript]
scala> def primeiro (x: Int, y: => Int) = x
primeiro: (x: Int,y: => Int)Int
[/cc_javascript]

O que definimos agora foi que o segundo parâmetro de ‘primeiro‘ será uma função que devolve um Int. Assim seu resultado só será obtido quando for feita a chamada direta na expressão. Agora conseguimos fazer a chamada em nossa função:

[cc_javascript]
scala> primeiro(0, loop)
res0: int = 0
[/cc_javascript]

Nossa função executou e finalizou sem problemas dessa vez, isso porque não foi necessário utilizar o segundo valor para obter o resultado. Essa é chamada por nome, e o primeiro exemplo é a chamada por valor.

Até agora vimos como invocar funções, como definir funções (def) e como definir variáveis (var). Vimos também o básico de inferência de tipo pelo compilador do scala e os primeiros conceitos da linguagem. Mas como eu havia dito, Scala também tem conceitos de orientação a objetos, portanto vamos definir nossa primeira classe em Scala:

[cc_javascript]
scala> class AcumulaSoma { var acumulado = 0; def acumula(i: Int) = { acumulado = acumulado + i; acumulado } }
defined class AcumulaSoma
[/cc_javascript]

Lembre-se que não somos obrigados a definir o tipo devolvido pela função caso esteja explícito e também não precisamos da palavra reservada ‘return‘. O que fizemos foi definir uma classe que se chama ‘AcumulaSoma‘ que tem um atributo, que por padrão do Scala é de acesso privado, que se chama acumulado e que o compilador inferiu o tipo inteiro baseado no valor atribuído. Também definimos para a classe a função ‘acumula‘ que recebe um inteiro, guarda o valor acumulado até então da soma com o parâmetro passado e devolve tal valor. Vamos instanciar um objeto da nossa classe e fazer a chamada ao método:

[cc_javascript]
scala> val ac = new AcumulaSoma
ac: AcumulaSoma = AcumulaSoma@cbbe37
[/cc_javascript]

Algumas diferenças com o Java (supondo que estamos mais acostumados com a sintaxe do Java) já podem ser notadas. Além da inferência do tipo de ‘ac‘ para ‘AcumulaSoma‘, criamos ‘ac‘ com a a palavra reservada ‘val‘, ou seja, estamos dizendo que diferente de ‘var‘ onde criamos uma variável, estamos criando um valor e estamos garantindo em tempo de compilação que este valor não pode ser alterado no código. No Java teríamos que usar a palavra ‘final‘ para obter o mesmo comportamento, em Scala temos que “satisfazer menos o compilador“. Também não foi necessário usar ‘()‘ para invocar o construtor de AcumulaSoma, e assim como no Java toda classe em Scala tem por padrão o construtor que não recebe parâmetros com acesso público. Continuando o exemplo:

[cc_javascript]
scala> ac.acumula(2)
res0: Int = 2

scala> ac.acumula(1)
res1: Int = 3
[/cc_javascript]

Nada de novidade aqui, estamos invocando a função ‘acumula‘ no objeto ‘ac‘. Porém ‘acumula‘ é uma função que recebe um único parâmetro e devolve um valor. Dessa forma podemos executar a chamada da nossa função da outra forma que vimos anteriormente:

[cc_javascript]
scala> ac acumula 4
res2: Int = 7
[/cc_javascript]

No Java conseguimos definir classes estáticas. Em Scala não existe exatamente o conceito de classes, atributos e parâmetros estáticos, porém existe um conceito mais bem definido: ‘object‘. Criamos um ‘object‘ igual criamos uma classe, com a diferença de que não é possível instanciarmos um object, afinal estamos definindo o próprio. Com essa diferença, estamos garantindo em tempo de compilação que o design do código, em que foi tomada a decisão de restringir a existência de apenas uma definição para determinada classe seja seguida. Em Java também é possível garantir tal critério em tempo de compilação, mas demanda a escrita de bem mais código e de uma compreensão um pouco maior do funcionamento e limitações da linguagem. Um exemplo de object:

[cc_javascript]
scala> object Objeto { def valor = 5 }
defined module Objeto

scala> Objeto.valor
res3: Int 5
[/cc_javascript]

Existem os tipos primitivos básicos em Scala como Int, Long, Boolean, Float, Double, Short, Byte, Char e String, sendo que String é a String do ‘java.lang‘. Um fato interessante sobre Scala é que como a linguagem roda em cima da JVM, todas as bibliotecas disponíveis para Java também estão disponíveis para Scala.

O tipo que não vimos ainda é o ‘void‘. Em Scala não existe o conceito de void, toda função devolve um valor e quando não há um valor explícito a ser devolvido o compilador se encarrega de devolver um valor do tipo ‘Unit‘. Exemplo:

[cc_javascript]
scala> def imprime = print(“Hello!”)
imprime: Unit
[/cc_javascript]

‘print’ é uma função que imprime um valor no terminal e não devolve valor, logo nossa função ‘imprime‘ também não devolve valor, portanto o tipo devolvido é ‘Unit‘, que também pode ser expressado/escrito como ‘()‘. A diferença básica entre void e Unit, é que Scala permite criar valores e variáveis do tipo Unit, e em Java por exemplo não é possível criar uma variável do tipo void. Também é possível analisar o valor devolvido por qualquer função mesmo que a função não devolva um valor definido e neste caso será um Unit. Um exemplo trivial de como usar este comportamento:

[cc_javascript]
scala> def num = 9
num: Int

scala> def verificaUnit(f: => Any) = { f match { case x:Unit => print(“Eh Unit!”) case _ => print(“Nao eh Unit!”) } }
verificaUnit: (f: => Any)Unit
[/cc_javascript]

O tipo ‘Any‘ é análogo ao ‘Object‘ do Java, portanto pode ser qualquer valor, função ou objeto.

Nessa função estamos usando um tipo de switch case para obter o mesmo comportamento do ‘instanceof‘ em Java, afinal não existe tal operação em Scala. O que está acontecendo é que a função verificaUnit recebe uma função que não recebe argumentos e devolve um valor qualquer. A função ‘f‘ é então evaluada e o resultado da função é testado nos cases da operação ‘match‘. Caso seja do tipo Unit, imprimimos “Eh Unit!”, e no caso padrão imprimimos “Nao eh Unit!”. Repare que diferente do ‘switch‘, no ‘macth‘ não temos que chamar a operação ‘break‘ para não executar os outros casos. Para testar verificaUnit vamos fazer a chamada usando nossas outras duas funções que foram definidas:

[cc_javascript]
scala> verificaUnit(num)
Nao eh Unit!
scala> verificaUnit(imprime)
Hello!Eh Unit!
[/cc_javascript]

Um exemplo mais prático



Vamos implementar um exemplo um pouco mais prático que calcula a raiz quadrada aproximada de um número. Para isso vamos usar o método de Newton de aproximações sucessivas. A idéia é bem simples, nada mais é do que uma busca binária entre os palpites que se aproximam cada vez mais da raiz quadrada do número passado. Vamos começar definindo uma função que diz se a raiz encontrada é boa o suficiente para ser o resultado:

[cc_javascript]
scala> def abs (x: Double) = if (x > 0) x else -x
abs: (x: Double)Double

scala> def proximaOSuficiente(palpite: Double, x: Double) = abs((palpite * palpite) – x) < 0.0001 proximaOSuficiente: (palpite: Double,x: Double)Boolean [/cc_javascript]

O bloco if é igual a sintaxe usada no Java, o mesmo vale para o else e o else if. Na segunda função estamos verificando se o número elevado ao quadrado é próximo o bastante do original. Agora vamos definir a função que faz um palpite melhor:

[cc_javascript]
scala> def melhoraPalpite(palpite: Double, x: Double) = (palpite + x / palpite) / 2
melhoraPalpite: (palpite: Double,x: Double)Double
[/cc_javascript]

O que fizemos foi pegar um número para palpite que está entre o nosso palpite anterior e x dividido sobre o palpite anterior, onde x é o número que queremos obter a raiz quadrada. O método de Newton garante que para x >= 1 essa aproximação é sempre válida. Vamos agora a função que executa o método de Newton propriamente dito:

[cc_javascript]
scala> def raizNewton(palpite: Double, x: Double): Double =
| if (proximaOSuficiente(palpite, x)) palpite else raizNewton(melhoraPalpite(palpite, x), x)
raizNewton: (palpite: Double,x: Double)Double
[/cc_javascript]

Estamos calculando a raiz quadrada de modo recursivo até que o resultado seja bom o suficiente. Agora basta definir uma base para nossa função, para isso iremos pré-definir um valor para o palpite base, guardando a chamada nesse escopo em uma outra função:

[cc_javascript]
scala> def raizQuadrada(x: Double) = raizNewton(1, x)
raizQuadrada: (x: Double)Double
[/cc_javascript]

Pronto, basta brincar com algumas chamadas para nossa função que calcula a raiz quadrada =).

[cc_javascript]
scala> raizQuadrada(169)
res4: Double = 13.000000070110696
[/cc_javascript]

O resultado obtido não é muito preciso, mas também essa não é a melhor maneira de se calcular a raiz quadrada, inclusive para números muito grandes o algorítmo não funciona por restrições com a precisão do Double.

Algumas Considerações



A grande moda das linguagens funcionais está surgindo porque acreditasse que elas são o futuro para a programação concorrente e paralela, de que se o seu programa foi escrito em uma linguagem funcional você terá ganho o benefício da execução concorrente sem grandes esforços, bem, isso não é verdade. Linguagens funcionais como qualquer outro paradigma necessitam que o programa seja escrito de maneira que seu design seja voltado para execução concorrente ou paralela, a difenrença é que a sintaxe e as ferramentas disponíveis nas linguagens funcionais facilitam tal design.

Existem algumas técnicas de programação que surgiram com linguagens funcionais, como por exemplo Recursão em Cauda, que não entrarei no mérito nesse post mas que vale a pena pesquisar sobre o assunto. Tais técnicas facilitam a vida do compilador no momento em que for possível ser feita algum tipo de otimização.

Uma comparação que logo surge na cabeça dos programadores é: qual linguagem é melhor, Scala ou Java? A resposta é fácil… nenhuma. Cada linguagem tem um escopo e paradigma de atuação diferente, não tem como comparar ambas. O que talvez faça sentido comparar sejam fatores mais fora do domínio da linguagem, como por exemplo o tamanho da comunidade que utiliza efetivamente a linguagem, o tipo de suporte, a documentação disponível, as restrições de uso, mas em geral depende do caso de uso específico para o seu problema, e principalmente do gosto do programador.

Por enquanto é isso, em um próximo post tratarei de alguns outros tipos de funções e falarei um pouco mais sobre currying. Também darei exemplos de tipos diferentes de declarações de classes e objetos.

Referência: Scala By Example, Martin Odersky

3 Comments

  • […] Este post é a continuação do primeiro post sobre Scala onde eu explico como instalar e como entender o básico da sintaxe. Caso você não tenha visto ainda, vale a consulta rápida: primeiros passos com Scala. […]

  • Davi disse:

    Muito bom a forma que vc escreveu, bem simples, estava procurando algo assim, a maioria dos exemplos que eu vi focam somente na programação funcional, mais fazem com que o codigo não fique legivel.
    Parabens

  • Sandys Nunes disse:

    Muito bom este post, comecei a estudar Scala a pouco tempo. Gostei da sua abordagem, pois compreendi tudo.
    Parabéns.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Compartilhe isso: