Scala para o Mundoj - Faculdades Integradas do Vale do Ivaí · Em Scala, há muitas formas de se...

10
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 ca- racterí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 pron- tamente Scala como uma linguagem adequada. Chad estava certo! Scala realmente oferece diversos recursos para pro- gramação concorrente, que iremos ver neste artigo, mas ela vai além dis- so. 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 corpora- tivas 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-Eval- Print Loop). artigo 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. C 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 on- line 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 para o Mundoj

Transcript of Scala para o Mundoj - Faculdades Integradas do Vale do Ivaí · Em Scala, há muitas formas de se...

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 ca-racterí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 pron-tamente Scala como uma linguagem adequada.

Chad estava certo! Scala realmente oferece diversos recursos para pro-gramação concorrente, que iremos ver neste artigo, mas ela vai além dis-so. 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 corpora-tivas 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-Eval-Print Loop).

a r t i g o

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.

C

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 on-line 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 para o Mundoj

53

Neste artigo será mostrado como Scala oferece todas essas possibilida-des. Para executar os exemplos apresentados no decorrer do artigo, siga as instruções do quadro “Executando código Scala”.

Executando código Scala

Sintaxe

Construtores

Overload de construtores em 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:

scala NomeDaClasse

Ou utilizando o próprio comando java:

java NomeDaClasse

O detalhe a observar é que quando utilizando o comando java, é neces-sário adicionar a biblioteca scala-library.jar ao classpath.

Por poder ser tratada como uma linguagem de script, há ainda a possibi-lidade 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 argu-

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.

Figura 1. Exemplo de um console Scala.

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 lingua-gens. 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

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 utili-zar qualquer classe Java compilada para bytecode.

class Project(val name: String, val modulesNames: List[String]) {

def start = {

println(“Project is started.”)

}

def runModule(module: () => Any) = {

module()

}

}

a declaração da classe Project que é seguida da declaração de seu construtor -

drã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

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).

-trutores 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

Constructores” para mais detalhes.

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:

54 www.mundoj.com.br

Tipagem estática

Scala não tem static

Type casting

Variáveis x Valores

Type Inference

No código anterior, declaramos a classe com seu construtor primário pa-

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.

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

-clarados. Diferente de como é feito no Java, os tipos vêm depois do identifi-cador, seguindo essa lógica: val identificador: Tipo. Veja o quadro Variáveis x Valores para entender o que significa o val antes do identificador.

Como manda o figurino das linguagens funcionais, Scala optou por seguir um caminho de imutabilidade de estado. Isso implica em ter atri-butos 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 palavra-chave 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 exem-plo, você não poderia modificar o conteúdo do valor nome, que resultaria em erro de compilação:

nome = “Novo Nome” // ERRO

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?

Em alguns momentos, Scala procura impor boas práticas de design, obrigando o desenvolvedor a buscar soluções mais robustas. Isso acon-tece, 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, cam-pos 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 é algo que pode parecer estranho aos olhos desatentos de um programador Java. A sintaxe é bem diferente. Considere o exemplo

-da se faz a tentativa de realizar o cast para duas outras classes.

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 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 cas-

ting, por exemplo.

O objetivo nesse caso é restringir a ação de uma operação a um determi-

nado tipo, ou mesmo diferenciar a forma como a ação é executada.

Um exemplo de Type Parametrization:

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.

Type Parametrization

55

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.

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 paco-tes 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 ob-ject 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 decla-rados no mesmo estilo de namespaces do C#, contendo as declarações de

Arquivos em Scala

package net.fratech {

package scala_intro {

import scala_intro.model.Module

class Project {

val module: Module

}

package model {

trait Module

}

package forJava {

object Car

}

}

}

class MethodsClass {

def aMethod = { println(“I have no parameters”) }

def aSecondMethod =

println(“I don’t have braces around my body”)

def printAMessage(msg: String) = {

println(msg)

}

def printAndReturn(msg: String): String = {

println(msg)

msg

}

def aMethodWithInferedReturnType(i: Int) = {

“The number is: “ + i

}

private def privateMethod(i: Int) = {

println(“I’m a private method.”)

}

def javaSimilar() {

println(“I don’t have the annoying ‘=’ sign.”)

}

// Compilation error

def likeInJavaWithReturn(): Int {

println(“I won’t compile. :(“)

}

private String justLikeJava() {

println(“I won’t compile. :(“)

}

}

Listagem 3. Exemplo de arquivo em Scala.

Listagem 4. Exemplos de declaração de métodos em Scala.

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 arma-

zenados em valores e variáveis, passadas como parâmetro e executadas

arbitrariamente por qualquer outro método.

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 funda-mental 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.

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 identifi-cador como o de uma variável.

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 identifi-cador 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.

O método aSecondMethod recebe um corpo de uma única linha, sem

chaves para delimitar. Isso só é possível quando o corpo é uma única ex-

pressão, que pode ser colocada na linha posterior à assinatura do méto-

do, como no exemplo. No método printAMessage, fica claro como definir

56 www.mundoj.com.br

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.

}

}

Listagem 5. Exemplos de funções em Scala

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.

Em printAndReturn o tipo de retorno é especificado. Isso facilita a visuali-zaçã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 aMe-thodWithInferedReturnType.

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 iTakeAFunctionWithPa-rams 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 desenvol-vimento 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

57

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:

y = seno(x)

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á.

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 eva-

luation, 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 Lis-

tagem 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. Re-

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

adicionado como um subproject do projeto inicial.

class Project {

var subprojects: List[Project] = List[Project]()

def start(m: String) = {

println(m)

}

def +(p: Project) = {

subprojects.add(p)

}

}

val project = new Project

val subproject = new Project

project start “message”

project.start(“message”)

project + subproject

Listagem 6. Exemplos de chamada de métodos com um único

parâmetro.

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, bastan-

do 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 tam-

bé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 chaman-

do 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 for-

ma, 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

58 www.mundoj.com.br

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

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.”)

}

def anotherConcreteMethod = {

println(“A concrete method on trait C”)

}

}

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

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

object MeuCarro {

private val myCar = new Carro(2010, “Audi A3”, “Felipe”)

def iniciar = myCar.iniciar

def mover = myCar.mover

def iniciarEMover = { myCar.iniciar; myCar.mover }

}

MeuCarro.iniciar

MeuCarro mover

MeuCarro iniciarEMover

val c = MeuCarro

c iniciar

c mover

c iniciarEMover

Listagem 7. Exemplo de classe em Scala.

Listagem 8. Herança e mixins.

Traits

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 Lis-

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, atra-vé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 pode-rí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.

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

59

Outra característica interessante de Scala para OO é o conceito de objetos singleton. São objetos que possuem uma única instância que é referen-ciada pelo identificador utilizado na declaração. Para declarar esse tipo

um exemplo de um singleton object. Como um singleton não pode ser instanciado, ele não possui construtor.

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.

object App {

def main(args: Array[String]) {

println(“Args: “ + args)

}

}

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

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

Companion Object

O mecanismo de singleton ainda oferece outra faceta, chamada Compa-nion 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 instan-

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

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 down-

-

Scala oferece um pacote chamado scala.swing, composto por classes que en-capsulam 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.

60 www.mundoj.com.br

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] {

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.

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))

})

contents += jTextPanel // Adicionamos o jTextPanel ao conteúdo

// deste BoxPanel.

background = color // Definimos a cor de fundo, diferenciando

// impares de pares.

}

}

}

-

te para o Twitter. // 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)

}

listenTo(updateField)

listenTo(updateButton)

listenTo(refreshButton)

/* A seguir são definidas as reações a eventos deste MainFrame. Utili-

zamos 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)

}

}

}

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.

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

61

é o caso do JTextPanel. Para utilizá-lo junto da API scala.swing, basta im-portar a classe e encapsulá-lo utilizando o método wrap(JComponent) do object Component.

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/.

Figura 2. Cliente para o Twitter utilizando swing e scala.

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, fica-ram de fora deste artigo por questão de espaço.

Além disso, o ecossistema de frameworks e ferramentas ao redor da lingua-gem é amplo e oferece diversas opções. Há frameworks interessantes para:

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 apre-ensã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

Referências