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