Scala para o Mundoj

Propaganda
artigo
Scala para o Mundoj
Felipe Rodrigues de Almeida
([email protected]), é engenheiro de Software com
8 anos na área de TI. Ao longo da carreira trabalhou
em projetos críticos para empresas como Novell,
IBM, Telefônica, CPqD e Embraer. Recentemente tem
se especializado em software de colaboração online e redes sociais, atuando na Fratech. Agilidade e
expressividade têm sido o seu dia-a-dia que varia entre
mentoring técnico e coaching de equipes ágeis. É um
dos criadores da Academia do Agile para a Fratech e
Globalcode.
Uma visão geral das funcionalidades e
conceitos por trás da linguagem mais
promissora e comentada do momento.
Scala é uma linguagem funcional e orientada
a objetos, que proporciona ferramentas
poderosas para programação concorrente e que
se integra muito bem com o ecossistema da JVM
e do framework .Net. Tem despertado muita
curiosidade e interesse em desenvolvedores
ao redor do mundo e tem sido citada
frequentemente como a próxima grande
linguagem. Enfim, muito tem se falado sobre
Scala ultimamente. Este artigo procura resumir
algumas das funcionalidades e características da
linguagem do ponto de vista de um programador
Java, através de exemplos simples e específicos,
que facilitam o entendimento.
52 www.mundoj.com.br
erta vez em uma entrevista, Chad Fowler, um dos mais afincos
rubystas do momento, foi questionado sobre quais eram as características que mais esperava para o futuro das linguagens de
programação. Sem pestanejar, ele respondeu: “Concorrência. As
linguagens de programação do futuro precisarão ter mecanismos para
programação concorrente, pois só assim irão tirar proveito do hardware
de múltiplas cores que estão surgindo.” Ao ser questionado sobre alguma
linguagem interessante para esse tipo de processamento, ele citou prontamente Scala como uma linguagem adequada.
C
Chad estava certo! Scala realmente oferece diversos recursos para programação concorrente, que iremos ver neste artigo, mas ela vai além disso. Para conseguir um bom isolamento de processos, é importante que
a linguagem tenha imutabilidade de estado, conseguido com uma boa
aplicação de programação funcional. Além disso, para uma linguagem
ter sucesso hoje em dia, é preciso ser concisa e enxuta. Oferecer formas
mais curtas sintaticamente (os famosos açúcares sintáticos). É preciso ser
portável e se integrar com o grande ecossistema de frameworks e APIs
disponíveis para as grandes plataformas como a JVM e o .Net framework.
Nessas plataformas rodam a esmagadora maioria de aplicações corporativas com as quais as novas deverão se integrar. Como se não bastasse,
Scala pode também ser utilizada como linguagem de Script, linguagem
compilada ou até mesmo de um bom console ao estilo REPL (Read-EvalPrint Loop).
"SUJHPt4DBMBQBSBP.VOEPK
Neste artigo será mostrado como Scala oferece todas essas possibilidades. Para executar os exemplos apresentados no decorrer do artigo, siga
as instruções do quadro “Executando código Scala”.
Executando código Scala
Para executar código Scala há várias opções. Scala é uma linguagem
compilada, portanto, possui um compilador que pode ser acionado
através do comando scalac. O compilador irá gerar um arquivo .class
contendo bytecode para a JVM. A partir daí podemos executar esse .class
utilizando o comando scala:
algumas surpresas que podem se tornar uma dificuldade no início.
A primeira coisa a se notar é que Scala, quando compilada, resulta
num bytecode muito similar ao bytecode do Java. Isso quer dizer que
a integração entre as duas linguagens é nativa, em nível de bytecode.
Para o Java, basta saber que há uma classe compilada no classpath
que ele irá utilizá-la. O mesmo acontece do lado do Scala. Pode utilizar qualquer classe Java compilada para bytecode.
-JTUBHFN6NBTJNQMFTDMBTTFFN4DBMB
class Project(val name: String, val modulesNames: List[String]) {
def start = {
println(“Project is started.”)
}
scala NomeDaClasse
Ou utilizando o próprio comando java:
java NomeDaClasse
def runModule(module: () => Any) = {
module()
}
O detalhe a observar é que quando utilizando o comando java, é necessário adicionar a biblioteca scala-library.jar ao classpath.
Por poder ser tratada como uma linguagem de script, há ainda a possibilidade de executar código scala sem compilar (na verdade a compilação
ocorre, porém, de forma transparente). Para isso, basta salvar seu código
em um arquivo com a extensão .scala e utilizar o comando scala para
executá-lo, como no exemplo abaixo:
scala arquivo.scala
Scala ainda oferece a possibilidade de testar código em seu console no
melhor estilo REPL. Para acessar o console, basta digitar scala sem arguNFOUPT7FKBOBmHVSBPDPOTPMF4DBMBFNBÎÍP
Para aqueles que gostam de utilizar o maven, há ainda um plugin para
scala que suporta a compilação e a execução de código Scala integrado
ao ciclo de vida de projetos maven.
}
0CTFSWBOEPB-JTUBHFNQPEFNPTWFSPCÈTJDPEBTJOUBYFEF4DBMB7FNPT
a declaração da classe Project que é seguida da declaração de seu construtor
QBESÍPWBMOBNF4USJOHWBMNPEVMFT/BNFT-JTU<4USJOH>
0DPOTUSVUPSQBdrão é sempre definido imediatamente após a declaração da classe. Além
deste construtor, scala ainda oferece construtores auxiliares.
Uma simples declaração de classe nos revela alguns detalhes. Observe que
não utilizo a palavra-chave public, pois, ao contrário do Java, em Scala classes,
atributos e métodos já são públicos por padrão. Podemos verificar também
RVFPVTPEFQPOUPFWÓSHVMB
ÏPQDJPOBMQBSBBÞMUJNBEFDMBSBÎÍPEFVNB
linha, mas necessário para separar múltiplas declarações em uma só linha.
Não há a necessidade de declarar o retorno usando a palavra chave return
que é opcional também.
Há ainda a declaração de dois métodos: start que não recebe parâmetros e
não retorna nada e runModule que recebe uma função como parâmetro e
simplesmente executa a função em seu corpo (mais sobre isso neste artigo).
Construtores
"JOEBOB-JTUBHFNQPEFNPTPCTFSWBSBTJOUBYFEFEFmOJÎÍPEPTDPOTtrutores em Scala. O construtor é declarado junto com as declarações da
classe, recebendo parâmetros dentro de parênteses. No caso da classe
Project, seu construtor recebe dois parâmetros: name que é do tipo String
FNPEVMFT/BNFTRVFÏEPUJQP-JTU<4USJOH>7FKBPRVBESPi0WFSMPBEEF
Constructores” para mais detalhes.
Figura 1. Exemplo de um console Scala.
Sintaxe
Uma das características mais marcantes de Scala é sua sintaxe, a qual
pode ser considerada única, porém com heranças de diversas outras
linguagens. Há um mito de que Scala possui sintaxe similar ao Java,
porém é fácil identificar recursos vindos do C#, Ruby e outras linguagens. Em Scala, há muitas formas de se chegar ao mesmo resultado.
Para aqueles que vêm de anos de programação em Java, Scala reserva
Overload de construtores em Scala
Além da declaração do construtor padrão, Scala oferece a possibilidade
de overloading de construtores. A sintaxe para isso é simples, porém
diferente de como é feito no Java. Considere o seguinte exemplo:
DMBTT1SPKFDUWBMOBNF4USJOHWBMNPEVMFT/BNFT-JTU<4USJOH>
\
EFGUIJTWBMOBNF4USJOH
UIJTOBNF-JTU<4USJOH>
^
53
"SUJHPo4DBMBQBSBP.VOEPK
No código anterior, declaramos a classe com seu construtor primário paESÍPOBNFTNBMJOIBDPNPFYQMJDBEPOB-JTUBHFN/BMJOIBTFHVJOUF
criamos um construtor auxiliar realizando um overload do construtor
primário, através da definição um método chamado this. Vale a pena
observar que o corpo deste método chama o construtor original. Isso é
obrigatório em Scala.
Você pode criar quantos construtores quiser em sua classe e chamar
qualquer um dos construtores a partir de outro, desde que em algum
ponto no stack de chamadas o construtor original seja chamado.
Tipagem estática
Scala é uma linguagem de tipagem estática. Isso quer dizer que os tipos de
variáveis e valores não mudam. Vale notar que nem sempre é necessário
declarar o tipo de uma variável. Veja o quadro Type Inference para mais
EFUBMIFT"-JTUBHFNUBNCÏNOPTSFWFMBBGPSNBDPNPPTUJQPTTÍPEFclarados. Diferente de como é feito no Java, os tipos vêm depois do identificador, seguindo essa lógica: val identificador: Tipo. Veja o quadro Variáveis
x Valores para entender o que significa o val antes do identificador.
Variáveis x Valores
Como manda o figurino das linguagens funcionais, Scala optou por
seguir um caminho de imutabilidade de estado. Isso implica em ter atributos e parâmetros que não variam seu conteúdo. Porém, para atender a
necessidades específicas da Orientação a Objetos, Scala deveria oferecer
variáveis (atributos que podem mudar de valor).
Para declarar um indicador que muda de valor, basta utilizar a palavrachave var. Ex:
var nomeVariavel: String = “Felipe”
Para declarar um indicador que representa um valor e não uma variável,
ou seja, não pode mudar de valor, utilize a palavra-chave val. Ex:
val nome: String = “Felipe”
Utilizar um valor (val) implica em não poder reatribuir valores. Por exemplo, você não poderia modificar o conteúdo do valor nome, que resultaria
em erro de compilação:
nome = “Novo Nome” // ERRO
Type Inference
Algo que me surpreende em Scala é a qualidade de seu compilador.
Scala goza de um ótimo compilador, com funcionalidades de alto nível
e performance razoável. O compilador suporta até mesmo plugins para
estender as funcionalidades da linguagem.
A robustez desse compilador pode ser conferida observando como Scala
é capaz de inferir tipos a variáveis e o tipo retorno de métodos. Em outras
palavras, o compilador é capaz de “descobrir” o tipo do objeto a partir
do tipo de seu valor, em tempo de compilação. O compilador procura
nas expressões algo que revele qual o tipo do objeto que resultará da
expressão.
Dá para imaginar a complexidade que é construir tal compilador?
54 www.mundoj.com.br
Scala não tem static
Em alguns momentos, Scala procura impor boas práticas de design,
obrigando o desenvolvedor a buscar soluções mais robustas. Isso acontece, por exemplo, no caso de métodos e atributos estáticos. Scala não
possui a palavra-chave static, o que quer dizer que você não pode criar
um método ou atributo que pertença à classe e não a uma instância
específica. Do ponto de vista de orientação a objetos mais purista, campos e métodos static são vistos como algo ruim. Scala busca as raízes da
OO lá no Smalltalk e evita esse problema. Além disso, segundo Martin
Odersky, criador da linguagem Scala, campos e métodos static podem
parecer legais, mas têm a chata tendência de complicar as coisas e limitar
a escalabilidade.
Type casting
Type casting é algo que pode parecer estranho aos olhos desatentos de
um programador Java. A sintaxe é bem diferente. Considere o exemplo
EB-JTUBHFNPOEFÏDSJBEBVNBJOTUÉODJBEBDMBTTF1SPKFDUFFNTFHVJda se faz a tentativa de realizar o cast para duas outras classes.
-JTUBHFN&YFNQMPEFDBTUJOHFN4DBMB
val project = new Project(“Project 1”, List[String](“module 1”, “module 2”))
project.asInstanceOf[Any] // O casting ocorre normalmente
project.asInstanceOf[String] // ClassCastException
Utilizamos o método asInstanceOf que pertence à classe Any (todos os
objetos em Scala derivam de Any) e que possui o tipo parametrizado. Veja
o quadro Type Parametrization.
Outra característica que vale a pena observar é a verbosidade necessária
para realizar um type casting em Scala. Isso é intencional, pois os criadores
querem desencorajar esse tipo de atitude. Ao invés disso, recomendam o
uso de pattern matching, um poderoso recurso disponível em Scala.
Type Parametrization
Type Parametrization significa configurar uma instância de objeto ou
método, passando um determinado tipo como parâmetro. Esse tipo
pode ser utilizado para as mais diversas coisas, como para um type casting, por exemplo.
O objetivo nesse caso é restringir a ação de uma operação a um determinado tipo, ou mesmo diferenciar a forma como a ação é executada.
Um exemplo de Type Parametrization:
WBSNPOUIT.BQ<4USJOH*OU>
Neste exemplo, estamos limitando os tipos da chave e dos valores do
Map. Isso faz com que o compilador possa identificar pontos em que essa
restrição é violada ou pontos em que ela possa ser violada em tempo de
execução. Para isso, basta acrescentar o tipo, envolvido entre colchetes,
imediatamente após o identificador a ser parametrizado. Java possui
uma funcionalidade muito similar a essa, chamada Generics, que foi
introduzida no Java 5.
"SUJHPt4DBMBQBSBP.VOEPK
Tanto Scala quanto Java, nesse caso, utilizam Type Erasure, ou seja, as
informações de tipos parametrizados só estão disponíveis em tempo de
compilação. Isso ocorre porque Scala é compilada em bytecode para a
JVM e, consequentemente, herda alguns comportamentos do Java, o
que é bom para aqueles que já tem vivência na plataforma.
Funções que recebem outras funções como parâmetro ou que retornam
uma função, são chamadas de High Order Function, um termo fundamental para a prática da programação funcional e para a escalabilidade
das aplicações, seja através de recursos como lazy evaluation ou para a
flexibilidade na criação de DSLs.
Arquivos em Scala
A diferença entre função e método neste caso é que as funções são
lambdas, ou seja, funções anônimas. Função nesse caso é o corpo de um
método. Um método é um membro de uma classe e possui um identificador como o de uma variável.
Em Scala os arquivos não precisam ter o mesmo nome da classe pública
como no Java. Um arquivo .scala pode possuir quantas classes, traits,
objects você quiser. Todos podem ser ou não públicos. Um arquivo pode
também possuir classes de pacotes diferentes, se usada a sintaxe de pacotes como namespaces. A Listagem 3 mostra um exemplo com a declaração
da classe Project que contém um valor chamado module do tipo Module.
Module por sua vez é um trait declarado no pacote model. Há ainda o object Car, um tipo de singleton. (Traits e objects serão explicados no tópico
sobre Orientação a Objetos, neste artigo). Veja como os pacotes são declarados no mesmo estilo de namespaces do C#, contendo as declarações de
DMBTTFTUSBJUTFPCKFDUTEFOUSPEPADPSQPEFDBEBQBDPUF
Listagem 3. Exemplo de arquivo em Scala.
Para definir um método em Scala, utilizamos a palavra reservada def. Na
Listagem 4 vemos diversos exemplos de criação de métodos. O identificador aMethod armazena a função anônima que executa um println().
Dessa forma, aMethod é um método que não recebe parâmetros e o tipo
de retorno não foi especificado, ou seja, será inferido pelo compilador e
neste caso, será void.
Listagem 4. Exemplos de declaração de métodos em Scala.
class MethodsClass {
def aMethod = { println(“I have no parameters”) }
package net.fratech {
def aSecondMethod =
println(“I don’t have braces around my body”)
package scala_intro {
import scala_intro.model.Module
def printAMessage(msg: String) = {
println(msg)
}
class Project {
val module: Module
}
def printAndReturn(msg: String): String = {
println(msg)
msg
}
package model {
trait Module
def aMethodWithInferedReturnType(i: Int) = {
“The number is: “ + i
}
}
package forJava {
private def privateMethod(i: Int) = {
println(“I’m a private method.”)
}
object Car
}
def javaSimilar() {
println(“I don’t have the annoying ‘=’ sign.”)
}
}
}
// Compilation error
def likeInJavaWithReturn(): Int {
println(“I won’t compile. :(“)
}
O compilador Scala irá gerar o bytecode e armazená-lo em arquivos .class
separados, cada um em seu respectivo pacote. A Listagem 3 também nos
remete aos tradicionais namespaces de C#, mostrando que nem só de
Java “viverá o homem”.
Métodos e funções
Em Scala há vários formatos de métodos e funções. Por se tratar de uma
linguagem funcional, as funções são tratadas como cidadãos de primeira
classe. Isso quer dizer que as funções são objetos que podem ser armazenados em valores e variáveis, passadas como parâmetro e executadas
arbitrariamente por qualquer outro método.
private String justLikeJava() {
println(“I won’t compile. :(“)
}
}
O método aSecondMethod recebe um corpo de uma única linha, sem
chaves para delimitar. Isso só é possível quando o corpo é uma única expressão, que pode ser colocada na linha posterior à assinatura do método, como no exemplo. No método printAMessage, fica claro como definir
55
"SUJHPo4DBMBQBSBP.VOEPK
um parâmetro para o método. O tipo de retorno nesse caso é void. Parâmetro
deve ter seu tipo especificado, caso contrário poderia gerar problemas de
sobrescrita e sobrecarga, além de dificultar a manutenção do código.
Listagem 5. Exemplos de funções em Scala
class Functions {
var holdingAFunction = (x: String) => println(x) // Atribuição de uma
//função que recebe uma String à variável holdingAFunction
def iTakeAFunctionWithNoParams(b: String)(cls: => String) = { // Declaração
// de um método que recebe uma função que não recebe parametros e,
// a armazena no parametro cls
println(cls + b) // Chamada da função armazenada no parametro cls.
// Seu resultado será somado ao valor do parametro b e será impresso
/ pelo metodo println
}
def iTakeAFunctionWithParams(b: String, f: String => String) = {
// Declaração de um método que recebe uma função que é armazenada
// no parametro f. A função recebe uma string como parametro.
println(f(b)) // Chamada da função armazenada no parametro f,
//passando o valor de b como parametro para a função.
}
def methodWithLocalFunction = {
def aFunction(b: String) = { // Declaração de uma função local, visivel
// somente dentro do método methodWithLocalFunction
println(b)
}
aFunction(b) // Chamada da função local. Funciona somente dentro do
//método methodWithLocalFunction
}
def callIt = {
holdingAFunction(“running a function literal”)
iTakeAFunctionWithNoParams(“a string”) { “The closure: “ }
// Chamada de método passando uma string e depois uma função como
// parametro
iTakeAFunctionWithParams(“a string”), b => { println(b); “The closure: “ } )
// Chamada de método passando uma string e depois uma função
//como parametro
iTakeAFunctionWithParams(“a string”, println _) // Chamada de método
// passando uma string e depois uma função como parametro
methodWithLocalFunction()// Chamada de método que contém uma
// função local definida
aFunction(“message”) // Compilation Error. Essa função só é visivel
// dentro do método methodWithLocalFunction.
}
}
56 www.mundoj.com.br
Em printAndReturn o tipo de retorno é especificado. Isso facilita a visualização do que pode ser retornado por um método. No entanto, é possível
confiar no compilador para inferir o tipo de retorno de um método a
partir da última expressão do corpo do método, como no caso de aMethodWithInferedReturnType.
Vemos ainda como declarar um método private e como declarar um
método sem utilizar o símbolo de = no método javaSimilar. Entretanto,
como mostrado nas declarações de likeInJavaWithReturn e justLikeJava,
não é possível utilizar a mesma sintaxe do java para todos os casos.
Em relação a funções, scala é bem generosa, oferecendo funções locais,
funções anônimas e closures. Uma função local nada mais é do que uma
função definida dentro de um escopo limitado. Uma função possui um
tipo, identificado pelos parâmetros e pelo tipo de retorno.
Na Listagem 5 vemos vários exemplos de manipulação de funções.
Armazenamos uma função na variável holdingAFunction, bastando
atribuir a mesma a forma literal de uma função. Criamos duas high order
functions, funções que recebem outra função como parâmetro ou que
retornam uma função.
O método iTakeAFunctionWithNoParams recebe como parâmetro uma
função que não recebe parâmetros. O método iTakeAFunctionWithParams mostra como se deve especificar o tipo do parâmetro que uma
função recebe, apesar de isso não ser obrigatório. No método callIt,
vemos como executar essas funções, passando tanto funções literais e
anônimas quanto uma função parcialmente aplicada, como é o caso da
sintaxe println _ que demonstra a aplicação parcial da função println,
resultando na função literal correspondente, sendo passada como parâmetro para iTakeAFunctionWithParams.
Partial Functions é o nome dado à técnica de extrair o valor literal da
função, ou seja, a função em si e não seu resultado. Esse valor literal da
função pode então ser movimentado entre outras funções até que seja
executado em algum momento. Para extrair o valor de uma função em
scala, basta posicionar o caracter _ logo após o indicador da função,
separado por espaço. Isso resultará em uma partial applied function,
pois scala irá resolver as variáveis e os parâmetros da função, tornando-a
fechada para ser executada em qualquer escopo.
Outra característica que vale a pena observar na Listagem 5 é o método
methodWithLocalFunction que possui uma função local, definida dentro
de seu corpo, nomeada aFunction. Uma função local é visível somente
dentro do escopo em que foi definida. Isso quer dizer que é possível
utilizá-la tranquilamente dentro do bloco em que foi definida.
Todas essas construções (e algumas coisas a mais) compõem o modelo
funcional de Scala. Utilizando esses recursos, a escalabilidade de sua
aplicação pode ser ampliada e situações extremamente difíceis de serem
resolvidas agora têm solução fácil. Isso implica num estilo de desenvolvimento diferenciado e que utiliza design patterns próprios, criando
uma maior curva de aprendizado para aqueles acostumados somente à
programação imperativa. Veja o quadro ” para mais detalhes”.
Programação funcional
Explicar programação funcional, por si só, daria um livro inteiro, por
isso não pretendo me aprofundar. Ao invés disso, vou descrever em
poucas palavras, o que é programação funcional, deixando a cargo do
"SUJHPt4DBMBQBSBP.VOEPK
leitor buscar mais informações sobre o assunto.
Analisemos uma função do ponto de vista matemático. Trata-se de uma
fórmula específica a ser aplicada para casos específicos. A função seno() é um
bom exemplo:
Listagem 6. Exemplos de chamada de métodos com um único
parâmetro.
class Project {
var subprojects: List[Project] = List[Project]()
y = seno(x)
def start(m: String) = {
println(m)
}
O identificador seno é o nome da função e serve para ativar a execução desta
função. seno possui uma fórmula para calcular seu resultado. Esta fórmula é
composta por diversas operações que compõe o “corpo” da função. A função
seno() é uma função pura, livre de side-effects, ou seja, não importa quantas
operações você realize no corpo desta função, o resultado será retornado e
nenhuma mudança de estado acontecerá.
def +(p: Project) = {
subprojects.add(p)
}
}
Imagine agora que você pode criar as funções que você quiser e dar o nome
que você bem entender. Definir qual é a fórmula que lhe trará o resultado
desejado e então utilizar esta função quando necessário. As funções podem
ser utilizadas em conjunto com o resultado de outras funções e seus resultados
podem servir de informação inicial de outras funções. É assim que descrevo a
programação funcional em sua forma mais pura. Um conjunto de funções que
se relacionam e criam os resultados esperados.
Trabalhar, dessa forma, sem regras imperativas e sem conservação de estado,
exige a aplicação de algumas técnicas, que irão permitir reúso, escalabilidade
e expressividade. É aí que entram conceitos como recursividade, lazy evaluation, high order functions, map, reduce, dentre outras. LISP é um ótimo
exemplo de linguagem funcional, pois trabalha somente com funções, sem
variáveis ou operadores.
Há ainda formatos mais flexíveis de programação funcional, como o proposto
por Scala, que adere a um mix de programação funcional com programação
orientada a objetos, permitindo utilizar o melhor dos dois mundos.
Scala possui recursos como lazy evaluation e tail recursion que revelam
sua natureza funcional. Além disso, não há operadores em Scala, tudo é
método. Outro forte indício é a existência de lambdas, as funções literais
que podem ser passadas ou retornadas como valores e serem executadas
em um outro escopo.
Para se aprofundar nesse assunto, recomendo a leitura do livro Structure and
Interpretation of Computer Programs da editora The MIT Press.
Scala ainda oferece uma sintaxe mais limpa para execução de funções. Na Listagem 6, vemos a definição de um método que recebe apenas um parâmetro.
Vemos também que podemos invocar este método de duas formas, utilizando
os pontos e parênteses ou sem utilizar os pontos e parênteses. Isso só é válido
para métodos que possuem apenas um parâmetro de entrada.
Esse mesmo mecanismo é utilizado para simular operadores em Scala. ReQBSFRVFOPDØEJHPPRVFBDPOUFDFOBWFSEBEFOÍPÏBFYFDVÎÍPEF
VNPQFSBEPSNBTTJNBDIBNBEBEBGVOÎÍP&TTBGVOÎÍPSFDFCFBQFOBT
um parâmetro e pode ser sobrescrita em seus objetos, assim como os outros
operadores, como =, *, /, <<. Isso quer dizer que esses são nomes válidos para
GVOÎÜFT"JOEBOB-JTUBHFNWFNPTBTPCSFFTDSJUBEPNÏUPEPOBQSÈUJDB
/FTUFDBTPRVBOEPSFBMJ[BNPTQSPKFDUOFX1SPKFDUPÞMUJNPQSPKFDUTFSÈ
adicionado como um subproject do projeto inicial.
val project = new Project
val subproject = new Project
project start “message”
project.start(“message”)
project + subproject
Orientação a objetos
Além do suporte à programação funcional, Scala possui um bom suporte
a orientação a objetos. Um suporte bem mais puro do que o suporte
oferecido por Java, já que em Scala não existem tipos primitivos e tudo é
considerado um objeto.
Classes
A criação de classes é bem simples, como já vimos em outras listagens, bastando para isso utilizar a palavra reservada class seguida de um identificador. Há
ainda o suporte a métodos construtores, primários e secundários. Scala também força o encapsulamento através de seus construtores. Na Listagem 7, a
classe carro é declarada. Os atributos ano, nome e dono são automaticamente
encapsulados. Repare que temos três tipos de parâmetros diferentes em nosso
construtor, var, val e um parâmetro sem especificação. Para o parâmetro var o
compilador o define como private e cria dois métodos, um para obter o valor
e outro para definir o valor. Na prática os métodos para o atributo dono são
dono(): String e dono_$eq(). Isso quer dizer que quando definimos um valor
para dono seguindo a sintaxe carro.dono =“Juca”estamos na verdade chamando o método dono_$eq() e quando tentamos obter o valor de dono através da
sintaxe carro.dono estamos na verdade invocando o método dono().
Para o atributo ano, que declaramos como sendo um valor (val), o compilador só
cria o método de obtenção do valor. Neste caso, o método ano(). Isso acontece
porque, em se tratando de um valor, não podemos reatribuir um valor à ano.
Por fim, para o atributo nome, não especificamos nem val e nem var. Dessa forma, este parâmetro será transformado em um attributo private sem métodos
de acesso e só estará disponível para uso interno em nossa classe. Além disso,
nome é por padrão um val implicando que qualquer reatribuição de valor
resultará em uma exception.
Para instanciar uma classe, basta utilizar o método new como em new
$BSSPi'VTDBwi'FMJQFw
57
"SUJHPo4DBMBQBSBP.VOEPK
Listagem 7. Exemplo de classe em Scala.
class Carro(val ano: Int, nome: String, var dono: String) extends Veiculo {
val modelo = nome + “ - “ + ano
def mover() = println(“girando a roda para a frente”)
}
trait Veiculo {
def iniciar = {
println(“Veículo ligado”)
}
def mover
}
val cr = new Carro(2010, “Fusca”, “Juca”)
cr.iniciar // -> Veículo ligado
cr.mover // -> girando a roda para a frente
Traits
tagem 7. O método iniciar é herdado e o método mover foi implementado
na classe Carro.
O mecanismo de herança é simples, os métodos concretos são herdados e
podem ser sobrescritos e os métodos abstratos devem ser implementados
ou então a classe deve ser declarada como abstrata. Para declarar uma
classe como abstrata, basta usar a palavra-chave abstract como mostrado
na Listagem 8.
Além disso, scala oferece suporte ao conceito de mixins. Isso quer dizer que
podemos misturar mais de um trait em uma única classe ou instância, através da palavra reservada with. Na Listagem 8 vemos um exemplo de como
podemos extender classes, traits e mixar traits em classes e em instâncias.
O conceito de Mixin pode parecer estranho aos olhos de programadores
Java, mas é uma funcionalidade muito querida em outras linguagens.
Como mostrado na Listagem 8, podemos simular uma herança múltipla,
misturando classes umas com as outras. A classe A extende a classe
abstrata B, porém, herdando (misturando-se) com C. Isso quer dizer que
todos os métodos que C possui passam a serem disponibilizados por
A. O mesmo acontece com a classe B no momento da criação de uma
instância de B. Como B é uma classe abstrata, normalmente não poderíamos criar uma instância, porém, como C implementa o único método
abstrato de B, podemos misturar as duas classes e assim conseguir um
objeto como em new B with C.
Listagem 8. Herança e mixins.
class A extends B with C
abstract class B {
def aMethod
def aConcreteMethod = {
println(“A concrete method on class B”)
}
}
trait C {
def aMethod = {
println(“I did it.”)
}
Scala também evita o problema de herança múltipla, não permitindo
misturar classes e traits que possuam a mesma assinatura de método.
Para verificar isso, renomeie o método anotherConcreteMethod para
aConcreteMethod no trait C. O resultado é que ao tentar compilar essa
nova versão, o compilador diz que C deveria sobrescrever o método
aConcreteMethod e, para isso, deveria extender B.
Singleton Object
-JTUBHFN4JOHMFUPOPCKFDU
object MeuCarro {
private val myCar = new Carro(2010, “Audi A3”, “Felipe”)
def anotherConcreteMethod = {
println(“A concrete method on trait C”)
}
def iniciar = myCar.iniciar
def mover = myCar.mover
def iniciarEMover = { myCar.iniciar; myCar.mover }
}
val a = new A // Cria uma nova instacia de A
a.aMethod // Chama o metodo aMethod, definido em B e implementado em C
a.aConcreteMethod // Chama o método aConcreteMethod definido em B
a.anotherConcreteMethod // Chama o metodo anotherConcreteMethod
// definido em C
}
MeuCarro.iniciar
MeuCarro mover
val b = new B with C // Criar uma instancia de B. Isso só é possivel porque
// misturamos com C que implementa o metodo aMethod que é abstrato.
// new B resultaria em exception na compilação.
b.anotherConcreteMethod // Chama o metodo anotherConcreteMethod
// definido em C
MeuCarro iniciarEMover
val c = MeuCarro
c iniciar
Scala não possui interfaces como em Java, mas em troca oferece os Traits.
Um Trait é como uma classe abstrata, que possui métodos concretos e
abstratos e pode ser extendida por outras classes. Veja um exemplo na Lis58 www.mundoj.com.br
c mover
c iniciarEMover
"SUJHPt4DBMBQBSBP.VOEPK
Outra característica interessante de Scala para OO é o conceito de objetos
singleton. São objetos que possuem uma única instância que é referenciada pelo identificador utilizado na declaração. Para declarar esse tipo
EFPCKFUPCBTUBVUJMJ[BSBQBMBWSBSFTFSWBEBPCKFDU/B-JTUBHFNWFNPT
um exemplo de um singleton object. Como um singleton não pode ser
instanciado, ele não possui construtor.
/B -JTUBHFN WFNPT DPNP DSJBS VNB DMBTTF DPN P GBNPTP NÏUPEP
main, herança do Java em Scala. Como não temos static em Scala, este
método só pode ser criado em singleton objects. Essa é a única forma de
simular métodos e variáveis static em Scala.
-JTUBHFN6NB"11DPNNÏUPEPNBJOFN4DBMB
object App {
def main(args: Array[String]) {
println(“Args: “ + args)
}
}
necessidade da palavra new na frente da construção. O método apply também
pode ser sobrecarregado, como qualquer outro método.
Scala + Swing + Twitter4J = Twitter Client
Para deixar o artigo um pouco mais interessante e exemplificar o uso de Scala
em uma aplicação mais real, foi desenvolvido um pequeno cliente para o
twitter. Foi utilizada uma API escrita em Java para o Twitter chamada Twitter4J
(http://www.twitter4j.org). A utilização é bem simples, bastando fazer o downMPBEEPKBSBUVBMNFOUFOBWFSTÍPFBEJDJPOÈMPOPDMBTTQBUIEBBQMJDBÎÍP
/BmHVSBQPEFTFDPOGFSJSPSFTVMUBEP0DØEJHPÏQFRVFOPFGÈDJMEFFOUFOEFS/B-JTUBHFNDSJBNPTPGSBNFQSJODJQBMRVFDPOUÏNPTDPNQPOFOUFT
Scala oferece um pacote chamado scala.swing, composto por classes que encapsulam e simplificam vários componentes Swing. Estas classes são simples
de usar, como o caso da classe SimpleGUIApplication e da classe MainFrame.
Há também classes que encapsulam os layouts managers, simplificando muito
o trabalho de posicionamento dos componentes.
-JTUBHFN$ØEJHPEPGSBNFQSJODJQBMEPDMJFOUFQBSBP5XJUUFS
Companion Object
O mecanismo de singleton ainda oferece outra faceta, chamada Companion Object. Um Ccompanion Object é um objeto que acompanha uma
classe. Uma classe “acompanhada” dessa forma pode ser chamada de
Companion Class. A técnica consiste em declarar um objeto e uma classe
no mesmo arquivo e com o mesmo identificador, mantendo o construtor
primário da classe como private. O segredo é que mesmo sendo private, o
construtor será visível a partir do Companion Object, permitindo a instanDJBÎÍPEBDMBTTFEFOUSPEPPCKFUP"-JTUBHFNNPTUSBFTUFFYFNQMP
-JTUBHFN&YFNQMPEF$PNQBOJPO0CKFDU
class Pessoa private (val age: Int)
object Pessoa {
def velha = new Pessoa(70)
def nova = new Pessoa(0)
def tipoOMatusalem = new Pessoa(970)
def apply(age: Int) = new Pessoa(age)
}
val bisavo = Pessoa velha
val sobrinha = Pessoa nova
val outraSobrinha = Pessoa(8) // Uso do metodo apply
val hebe = Pessoa tipoOMatusalem
O Companion Object é mais uma forma de encapsular as funcionalidades de
uma classe através de algo similar a uma abstract factory fashion. Imagine que
o Companion Object pode criar instâncias diferentes da companion classe,
dependendo de quais parâmetros ele recebe em um método. Além disso,
podemos utilizar o método apply, que simula um construtor, porém sem a
import swing._
import event.{ButtonClicked, ValueChanged}
import twitter4j.{Twitter, Status}
import scala.collection.jcl.Conversions._
import java.awt.{Dimension, Color}
import javax.swing.BorderFactory
object TwitterGUI extends SimpleGUIApplication {
val twitter: Twitter = new Twitter(“felipero”,”meu_password”)
// O constructor recebe o usuário e a senha
def top = new MainFrame { // definição do método top, cujo corpo é a
// criação de um único frame.
// Todo o código que vai dentro deste bloco será executado no escopo
// do construtor da classe MainFrame. Ou seja, podemos acessar
// campos private e protected desta instância. Isso pode ser feito para
// qualquer classe em Scala.
title = “A Simple Twitter Swing GUI” // Atribuição de um valor para o
// atributo title do MainFrame
preferredSize = new Dimension(430,600) // definindo o tamanho
// preferido, como em Swing normal.
val updateLabel= new Label { text = “Status:” }
val updateField = new TextArea(3, 50)
val updateButton = new Button { text = “Send” }
val refreshButton = new Button { text = “Refresh” }
var list = new ListView[Status](getStatusList) // Criando um list view
// que é parametrizado.
list.renderer = new StatusRenderer // Instanciação do renderer utilizado
// para a ListView.
list.fixedCellHeight = 70
list.fixedCellWidth = 350
contents = new BoxPanel(Orientation.Vertical) { // BoxPanel é um
// painel com layout dividido horizontalmente. Armazenamos no
// atributo contents da classe MainFrame.
contents += new FlowPanel(FlowPanel.Alignment.Left){ contents +=
updateLabel } // FlowPanel é um panel com FlowLayout. Este panel
59
"SUJHPo4DBMBQBSBP.VOEPK
// contem um label. O acrescentamos como um component ao contents
// do BoxPanel.
contents += updateField
contents += new FlowPanel {
contents += updateButton
contents += refreshButton
}
contents += new ScrollPane(list)
}
-JTUBHFN3FOEFSFSDVTUPNJ[BEPVUJMJ[BEPOP-JTU7JFXEPDMJFOte para o Twitter.
import swing.ListView.Renderer
import twitter4j.Status
import java.awt.{Dimension, Color}
import swing._
import javax.swing.{JTextPane, BorderFactory, ImageIcon}
class StatusRenderer extends Renderer[Status] {
listenTo(updateField)
listenTo(updateButton)
listenTo(refreshButton)
def componentFor(list: ListView[_], isSelected: Boolean, focused: Boolean,
status: Status, index: Int) = { // Implementação do método abstrato da
// classe Renderer, que recebe um tipo parametrizado.
// Este método erá chamado para cada item do ListView, retornando os
// componentes que compõe a linha de cada item.
/* A seguir são definidas as reações a eventos deste MainFrame. Utilizamos a funcionalidade de pattern matching do Scala para identificar quais
eventos ocorreram. Para cada case atribuimos um bloco de código que será
executado quando este case for alcançado. */
reactions += {
case ValueChanged(ta: TextArea) =>
updateLabel.text = “Status: (“ + ta.text.length + “)”
if(ta.text.length > 140) {
updateLabel.foreground = Color.RED
updateButton.enabled = false
} else {
updateLabel.foreground = Color.BLACK
updateButton.enabled = true
}
case ButtonClicked(b) =>
if(b == updateButton) {
twitter.updateStatus(updateField.text)
updateField.text = “”
updateList(list)
} else {
updateList(list)
}
val color = if(index % 2 == 0) new Color(229, 229, 242) else
new Color(197, 197, 228)
val avatarPanel = new BoxPanel(Orientation.Vertical) {
contents += new Label { icon = new ImageIcon(
status.getUser.getProfileImageURL) }
contents += new Label { text = status.getUser.getScreenName }
background = color
border = BorderFactory.createMatteBorder(0,0,0,1, Color.lightGray)
minimumSize = new Dimension(90, 70)
maximumSize = new Dimension(90, 70)
}
new BoxPanel(Orientation.Horizontal) {
contents += avatarPanel
val jTextPanel = Component.wrap(new JTextPane() { // Para exibir o
// status, optei por utilizar um JTextPanel. Neste caso precisamos
// encapsulá-lo em um scala.swing.Component através do método wrap.
// Definindo as properties do JTextPanel
setText(status.getText)
setBackground(color)
setPreferredSize(new Dimension(300, 50))
setMaximumSize(new Dimension(300, 50))
})
}
}
def updateList(list: ListView) = {
list.listData = getStatusList
list.repaint
}
def getStatusList = {
twitter.getFriendsTimeline().toSeq /* Aqui utilizamos o twitter4j para
obter o timeline dos seguidos. O ListView recebe um objeto do tipo Seq, por
isso, precisamos convertê-lo utilizando o método toSeq. Para isso é obrigatório importar o pacote scala.collection.jcl.Conversions._ */
}
}
Para a listagem de posts do twitter, utilizamos um componente chamado
ListView que encapsula o componente JList da API swing. Utilizamos
ainda um renderer customizado, para incluir a foto e o nome do usuário.
/B-JTUBHFNQPEFNPTPCTFSWBSBJNQMFNFOUBÎÍPEFTUFSFOEFSFS
60 www.mundoj.com.br
contents += jTextPanel // Adicionamos o jTextPanel ao conteúdo
// deste BoxPanel.
background = color // Definimos a cor de fundo, diferenciando
// impares de pares.
}
}
}
"DMBTTF4UBUVT3FOEFSFSFTUFOEFBDMBTTFBCTUSBUB-JTU7JFX3FOEFSFS<">
Esta classe é responsável por renderizar cada linha do ListView. Ela possuí
tipos parametrizados que permitem customizar qual é o objeto com o
qual ela trabalha. Neste caso, trabalhamos com objetos do tipo twitter4j.
Status, o qual representa um post no Twitter.
Como a API scala.swing não encapsula todos os componentes, é comum
precisarmos utilizar componentes diretamente da API javax.swing. Esse
"SUJHPt4DBMBQBSBP.VOEPK
é o caso do JTextPanel. Para utilizá-lo junto da API scala.swing, basta importar a classe e encapsulá-lo utilizando o método wrap(JComponent)
do object Component.
"JOEBOB-JTUBHFNBVUJMJ[BÎÍPEFBMHVNBTDMBTTFTEB"1*KBWBBXU
como é o caso da classe java.awt.Color, serve para demonstrar que o uso
de classes Java no código Scala é muito simples e a integração é nativa.
Para iniciar a aplicação, basta executar o comando scala TwitterGUI.scala
no mesmo diretório do código-fonte. O código-fonte deste cliente e do
artigo podem ser baixados em http://github.com/felipero/examples/
tree/master/scala_intro/.
Considerações finais
Um artigo é muito pouco para que falemos sobre todas as funcionalidades
da linguagem Scala. Neste artigo, foquei nas principais diferenças em
relação à Java e no básico da sintaxe. Há muito a ser dito ainda sobre esta
linguagem, porém ficou claro que não é impossível começar a se aventurar
no mundo funcional, mesmo para aqueles com um histórico baseado em
orientação a objetos.
Apesar de o artigo demonstrar aspectos importantes da sintaxe de Scala,
funcionalidades importantes como a API de actors para programação
concorrente, Pattern Maching e Case Classes para identificação de padrões
de mensagens e objetos, manipulação de XML como objetos normais em
Scala utilizando algo similar ao XPath para navegação, dentre outras, ficaram de fora deste artigo por questão de espaço.
Além disso, o ecossistema de frameworks e ferramentas ao redor da linguagem é amplo e oferece diversas opções. Há frameworks interessantes para:
UFTUFTOPFTUJMP#%%F5%%QBSBEFTFOWPMWJNFOUP8FCJODMVJOEPVNB"1*
Comet nativa (API utilizada para notificar clients de eventos ocorridos no
servidor, muito utilizado pelo google wave, por exemplo) e, ferramentas de
build, tanto escritas em Scala quanto utilizando maven ou ant.
Muitos projetos fora do Brasil utilizam Scala, porém ainda há certa apreensão quando se trata de contratar profissionais com experiência nesta
linguagem, que apesar de bem parecida com Java e C#, necessita de uma
curva de aprendizado. Há quem veja isso como uma oportunidade de se
EFTUBDBSKÈRVFBEFNBOEBQPSQSPmTTJPOBJTÏNBJPSEPRVFBPGFSUBt
Referências
t1SPHSBNNJOHJO4DBMBo.BSUJO0EFSTLZ-FY4QPPOF#JMM7FOOFST"SJUNB1SFTT
t1SPHSBNNJOH4DBMBo7FOLBU4VCSBNBOJBN5IF1SBHNBUJD1SPHSBNNFST
Figura 2. Cliente para o Twitter utilizando swing e scala.
61
Download