Escolar Documentos
Profissional Documentos
Cultura Documentos
Introduo
Este documento visa dar uma rpida introduo a linguagem Scala e seu compilador. Ele dirigido a pessoas que realmente possuem alguma experincia em programao e desejam uma viso geral da linguagem Scala. Assumimos que voc possui conhecimento bsico em conceitos de orientao a objeto, especialmente em Java.
Um primeiro exemplo
Como primeiro exemplo usaremos o clssico Hello world. Ele no um programa fascinante, mas com ele ca simples de demonstrar o uso de ferramentas Scala sem falar muito da linguagem. Ele car como abaixo:
object HelloWorld { def main(args: Array[String]) { println("Hello, world!") } }
A estrutura deste programa deve ser familiar para programadores Java: ela consiste de um mtodo chamado main que recebe os argumentos da linha de comando, um array de string, como parmetro; o corpo deste mtodo consiste de uma chamada nica ao mtodo pr-denido println que recebe nossa amigvel saudao como argumento. O cdigo de main no retorna um valor, dessa forma no necessrio declarar um valor de retorno. O que no to familiar assim para programadores Java a declarao object que contm o mtodo main. Essa declarao introduz o que comumente chamado de objeto singleton, que uma classe que possuir apenas uma nica instncia. A declarao acima contri tanto a classe chamada HelloWorld quanto sua intncia, tambm chamada de HelloWorld. Esta instncia criada sob demanda, no momento do seu primeiro uso. O leitor mais astuto pode ter percebido que o cdigo do mtodo main no declarado como static aqui. Isso ocorre por que membros estticos (mtodos ou campos) no existem em Scala. Ao invs de usar mtodos estticos, o programador Scala declara esses membros como objetos singleton.
2.2
Rodando o exemplo
Se voc salvar o cdigo acima num arquivo chamado HelloWorld.scala, voc pode o compilar usando o comando abaixo (o sinal de maior > representa o prompt de comando e no deve ser digitado):
> scalac HelloWorld.scala
Isso ir gerar uma srie de classes no diretrio corrente. Uma dessas classes ser chamada de HelloWorld.class, e contm a classe que deve ser diretamente executada pelo comando scala, como ser mostrado a seguir.
Um dos maiores poderes da Scala sua capacidade de integrao fcil com o Java. Todas as classes do pacote java.lang so importadas por padro, enquanto que todas as outras podem ser importadas explicitamente. Vamos ver um exemplo que demonstra isso. Ns precisamos obter e formatar a data corrente de acordo com a conveno usada num pas especco, que no nosso exemplo ser a Frana 1 . A biblioteca de classes Java dene um poderoso conjunto de classes utilitrias, como Date e DateFormat. Como Scala interage diretamente com Java, no existem classes equivalentes na biblioteca de classes Scala: ns simplesmente importamos os pacotes Java correspondentes:
import java.util.{Date, Locale} import java.text.DateFormat import java.text.DateFormat._ object FrenchDate { def main(args: Array[String]) { val now = new Date val df = getDateInstance(LONG, Locale.FRANCE)
1
Outras regies como os falantes de francs em parte da Suia usam a mesma conveno
O import da Scala bem parecido com o seu equivalente em Java, mas ainda mais poderoso. Multiplas classes podem ser importadas de um mesmo pacote atravs do uso de chaves, como na primeira linha. Outra diferena que quando desejamos importar todas as classes de um pacote usamos o sublinhado (underscore) (_) ao invs do asterisco (*). Isso ocorre por que o asterisco um identicador vlido em Scala (por exemplo, o nome de um mtodo), como veremos mais a frente. O import na terceira linha traz todos os membros da classe DateFormat. Isso faz o mtodo esttico getDateInstance e o campo esttico LONG diretamente visiveis. Dentro do mtodo main ns primeiro criamos uma instncia da classe Date do Java, que por padro contm a data atual. A seguir, ns denimos o formato da data usando o mtodo esttico getDateInstance, que importamos anteriormente. Finalmente, ns imprimimos a data atual formatada de acordo com a instncia localizada de DateFormat. Esta ltima linha mostra uma propriedade interessante da linguagem Scala: mtodos que recebem apenas um argumento podem ser usados com uma sintaxe no xa. Desta forma, a expresso
df format now
Isto pode ser um detalhe menor da sintaxe, mas possui consequncias importantes, as quais sero exploradas com mais detalhes na prxima seo. Para concluir esta seo sobre integrao com Java, voc deve notar que possvel herdar a partir de classes Java e implementar interfaces Java diretamente em Scala.
Tudo um objeto
Scala uma linguagem orientada a objetos pura, no sentido de que tudo um objeto, incluindo nmeros e funes. Isso difere de Java, j que Java distingue tipos primitivos (como Boolean e Int) de tipos de referncia, e no permite que sejam manipulados funes como valores.
4.2
Funes so objetos
consiste exclusivamente de chamadas de mtodos, por que ela equivalente a expresso abaixo, como vimos na seo anterior:
1.+(2.*(3./(x)))
Note que, para imprimir esta sentena, ns precisamos usar o mtodo println sem a necessidade do uso de System.out.
A presena de uma funo annima neste exemplo revelada pelo smbodo de => que separa os argumentos da funo de seu corpo. Neste exemplo, a lista de argumentos est vazia, o que pode ser visto pelo par de parenteses sem nada dentro a esquerda da echa. O corpo da funo o mesmo que tinhamos na extinta timeFlies, do exemplo acima.
Classes
Como vimos acima, Scala uma linguagem orientada a objetos, e dessa forma possui o conceito de classes.2 Essas classes, em Scala, so declaradas usando uma sintaxe muito parecida com a do Java. Uma das diferenas mais marcantes que classes em Scala podem ter parmetros. Isso ilustrado com a denio abaixo de uma classe para nmeros complexos:
class Complex(real: Double, imaginary: Double) { def re() = real def im() = imaginary }
Essa classe Complex recebe dois argumentos, que so a parte real e a parte imaginria de um nmero complexo. Estes argumentos devem ser passados no moPara evitar chateaes: sabemos que algumas linguagens orientadas a objeto no possuem o conceito de classes, mas Scala no uma dessas.
2
5.1
A classe contm dois mtodos, chamados re e im, que do acesso a ambas as partes do nmero. Repare que o tipo de retorno desses dois mtodos no especicado explicitamente. Ele ser denido automaticamente pelo compilador, que olha os mtodos e deduz que o valor de retorno de ambos um Double. O compilador, porm, pode no estar sempre apto a saber qual o tipo de retorno, e no h uma regra simples para saber qual o tipo que ele efetivamente usou. Na prtica isso no um problema visto que o compilador sabe que s pode mudar o tipo que no foi explicitamente passado. Como uma dica, o programador iniciante em Scala devem tentar omitir tipos quando esses forem fceis de perceber no contexto, e ver como o compilador se comporta. Aps algum tempo o programador ter um bom feeling sobre que tipo pode ou no omitir.
Seria interessante acessar a parte real e a parte imaginria como campos, sem a necessidade de colocar esse par de parenteses ao lado do nome. Isso pode ser feito em Scala atravs da denio de mtodos sem argumentos. Nossa classe Complex pode ser reescrita como abaixo:
class Complex(real: Double, imaginary: Double) { def re = real def im = imaginary }
obrigatrio especicar que o mtodo est sendo sobrescrito atravs do uso do modicador override, para evitar sobrescritas acidentais. Por exemplo, nosso cdigo da classe Complex pode ser ampliado com a redenio do mtodo toString, herdado da classe Object.
class Complex(real: Double, imaginary: Double) { def re = real def im = imaginary override def toString() = "" + re + (if (im < 0) "" else "+") + im + "i" }
Um tipo de estrutura de dados que costuma aparecer em softwares a rvore. Por exemplo, interpretadores e compiladores usualmente representam programas internamente como rvores; documentos XML so rvores; e diversos tipos de contineres so baseados em rvores. Ns agora iremos examinar como rvores so representadas e manipuladas em Scala atravs de um programa simples que simula uma calculadora. O objetivo deste programa manipular expresses aritmticas simples, compostas de somas, constantes do tipo inteiro e variveis. Dois exemplos dessas expresses so 1 + 2 e (x + x) + (7 + y). Ns precisamos primeiro decidir qual a representao que usaremos para as expresses. A mais natural uma rvores, onde os ns so as operaes (no nosso caso, adio) e o restante so os valores (no caso constantes e variveis). Em Java, uma rvore pode ser representada usando uma super-classe abstrata, e uma sub-classe concreta por sub-classes concretas. Em linguagens de programao funcionais ns podemos usar um tipo algbrico para o mesmo propsito. Scala possui o conceito de classes case que um meio termo entre ambos. Abaixo voc pode ver um exemplo onde denimos a rvore para nossa calculadora:
abstract class Tree case class Sum(l: Tree, r: Tree) extends Tree case class Var(n: String) extends Tree case class Const(v: Int) extends Tree
Perceba que, pelo fato das classes Sum, Var e Const serem declaradas como classes case, elas diferem de classes padro em vrios aspectos: a palavra chave new no necessria para criar instncias dessas classes (quer dizer que podemos escrever simplesmente Const(5) ao invs de new Const(5)),
funes do tipo getter so automaticamente denidas para os parmetros do construtor (por exemplo, possvel pegar o valor do parmetro v de uma instncia chamada c da classe Const apenas com c.v), a denio padro dos mtodos equals e hashCode so disponibilizadas, trabalhando com a structure das instncias e no com sua identidade. uma denio padro para o mtodo toString tambm disponibilizada, e imprime o valor na sua forma padro (por exemplo, a expresso x + 1 imprime Sum(Var(x),Const(1))), instncias dessas classes podem ser decompostas atravs de busca por padro como veremos mais a frente. Agora que ns temos denidos os tipos de dados que representam nossa expresso aritmtica ns podemos iniciar a denir os operadores para manipul-las. Nossas expresses iro iniciar com uma funo que ir avaliar a expresso em um ambiente. O objetivo do ambiente dar valores as variveis. Por exemplo, a expresso x + 1 avaliada em um ambiente que associa o valor 5 a varivel x, escreve {x 5}, dando 6 como resultado. Dessa forma temos que encontrar um jeito de representar ambientes. Ns podemos, claro, usar algumas estruturas de dados associativas, como uma tabela hash, mas ns podemos usar diretamente funes! Um ambiente nada mais do que uma funo que associad um valor a uma varivel. O ambiente {x 5} mostrado acima pode ser escrito como abaixo em Scala:
{ case "x" => 5 }
Esta notao dene uma funo que, quando recebe a string x como um argumento, retorna o inteiro 5, e gera uma exceo se for diferente disso. Antes de escrever a funo de avaliao deixe-me dar um nome ao tipo dos ambientes. Ns podemos, claro, sempre usar o tipo String => Int para ambientes, mas simplicaria o programa se ns introduzirmos um nome para este tipo, o que permite modicaes simples no futuro. Isso pode ser feito em Scala como mostrado abaixo:
type Environment = String => Int
A partir de agora o tipo Environment (ambiente) pode ser usado como um apelido para funes que vo de um String para um Int. Ns podemos agora denir a funo de avaliao. Conceitualmente ela muito simples: o valor de uma soma de duas expresses simplesmente o valor da soma dessas expresses; o valor da varivel obtido diretamente pelo ambiente e o valor da constante o valor da constante por s s. Expressar isso em Scala no mais difcil:
10
def eval(t: Tree, env: Environment): Int = t match { case Sum(l, r) => eval(l, env) + eval(r, env) case Var(n) => env(n) case Const(v) => v }
Essa funo de avaliao funciona realizando uma busca de padro na rvore t. Intuitivamente o signicado de algumas dessas denies pode car mais claro: 1. a primeira vericao da rvore t o Sum, ento criada uma sub-rvore auxiliar a esquerda numa varivel chamada l e uma sub-rvores numa varivel r, e ento segue com a avalio da expresso que est aps a echa; esta expresso pode (e faz) uso das variveis seguidas pelo padro que aparece antesda echa, l e r, 2. se a primeiraq vericao no for bem sucedida ento a rvore no um Sum, ento vericado se t um Var; se o ento ele anexa o nome contido em Var para uma varivel n e continua com a avaliao na expresso, 3. se a segunda vericao falha ento t no nem um Sum e nem um Var, ento ele verica se um Const, e se o for, ento ele anexa o valor contido no n em Const na varivel v e continua a vericao, 4. nalmente, se todas as vericaes falharem, uma exceo lanada para sinalizar a falha da busca por um padro; isso aconteceria aqui apenas se mais de uma sub-classe de Tree fosse declarada. Ns vericamos que a idia bsica da busca de padro tentar achar um valor numa srie de padres, e quando o padro encontrado, extrair e nomear as vrias partes do mesmo para, nalmente, avaliar um cdigo que tipicamente faz uso dessas partes nomeadas. Um programador experiente em orientaes a objetos pode car supreso por que no denimos eval como um mtodo da classe Tree e suas subclasses. Ns poderamos fazer isso sem problemas, visto que Scala possibilita a denio de mtodos em classes case assim como em classes normais. Decidir usar a busca por padro ou mtodos uma questo de gosto, mas isso possui implicaes importantes em relao a extensibilidade da aplicao: quando usamos mtodos ca fcil de adicionar um novo tipo de n, bastando para isto apenas den-lo em uma sub-classe da Tree; Em contrapartida, adicionar uma nova operao para manipular a rvore uma tarefa tediosa, pois isto requer modicaes em todas as subclasses de Tree quando usamos busca por padro a situao invertida: adicionar um novo tipo de n requer que modicao em todas as funes nas quais a busca por pado atua, para ter o novo n devidamente; Em contrapartida, adicionar
11
uma nova operao fcil, precisando apenar den-la como uma funo independente. Para explorar bastante buscas de padres, vamos denir outra operao com expresses aritmticas: derivao simblica. O leitor deve lembrar as seguintes regras referentes a esta operao: 1. a derivada de uma soma a soma de suas derivadas, 2. a derivada de qualquer varivel v um se v a varivel relativa na qual a derivao ocorre seno ser zero, 3. a derivada de uma constante zero. Estas regras podem ser traduzidas quase que literalmente para cdigo Scala, para obter a seguinte denio:
def derive(t: Tree, v: String): Tree = t match { case Sum(l, r) => Sum(derive(l, v), derive(r, v)) case Var(n) if (v == n) => Const(1) case _ => Const(0) }
Esta funo introduz dois novos conceitos relacionados busca de padres. Primeiramente, a expresso case para as variveis possui uma proteo, uma expresso que segue a palavra-chave if. Esta proteo previne que a busca de padres tenha sucesso a no ser que esta expresso seja verdadeira. Aqui usado para ter certeza que retornaremos a constante 1 apenas se o nome da varivel que est sendo derivada o mesmo o mesmo da varivel de derivao v. A segunda nova funcionalidade da busca de padres usada aqui o caractere curinga, escrito como underline, no qual uma busca de padres de qualquer valor, sem precisar nome-lo. Ns ainda no exploramos todo o poder da busca de padres, mas iremos parar por aqui para manter este documento resumido. Ainda queremos ver como as duas funes acima funciona num exemplo real. Para este propsito vamos escrever uma simples funo main, na qual realiza vrias operaes sobre a expresso (x + x) + (7 + x): primeiro computado seu valor no ambiente {x 5, y 7}, para ento computar sua derivada relativa a x e ento y.
def main(args: Array[String]) { val exp: Tree = Sum(Sum(Var("x"),Var("x")),Sum(Const(7),Var("y"))) val env: Environment = { case "x" => 5 case "y" => 7 } println("Expression: " + exp) println("Evaluation with x=5, y=7: " + eval(exp, env)) println("Derivative relative to x:\n " + derive(exp, "x")) println("Derivative relative to y:\n " + derive(exp, "y")) }
12
Examinando a sada, ns podemos ver que o resultado da derivada deve ser simplicada antes de ser apresentada ao usurio. Denir uma funo de simplicao bsica, usando busca de padres, uma problema interessante(mas surpreendentemente capcioso) e deixado como um exerccio para o leitor.
Mixins
Alm da herana de cdigo de uma superclasse, uma classe Scala tambm pode importar cdigo de um ou vrios mixins. Talvez a forma mais fcil para um programador java entender o que so mixins v-los como interfaces na qual podem tambm conter cdigo. Em Scala, quando uma classe sub-classe de mixin, ela implementa aquela interface mixin e herda todo o cdigo contido no nele. Para ver a utilidade dos mixins, veremos um exemplo clssico: objetos ordenados. Geralmente til ser capaz de comparar objetos de uma dada classe atravs delas mesmas, como por exemplo para orden-las. Em Java, objetos nos quais so comparveis implementam a interface Comparable. Em Scala ns podemos fazer um pouco melhor do que em Java, denindo nosso equivalente de Comparable como um mixin, no qual ns chamaremos de Ord Ao comparar objetos, seis diferentes predicados podem ser teis: menor, menor ou igual, igual, no igual, maior ou igual e maior. Contudo, denindo todos ele trabalhoso, especialmente considerando que quatro desses seis podem ser expressos usando os dois remanescentes, isto , dado os predicados igual e menor (por exemplo), um pode expressar os outros. Em Scala, todas estas observaes podem ser facilmente capturadas pela seguinte declarao do mixin:
trait def def def def } Ord { < (that: <=(that: > (that: >=(that: Any): Any): Any): Any): Boolean Boolean = (this < that) || (this == that) Boolean = !(this <= that) Boolean = !(this < that)
7 Mixins
13
Esta denio cria um novo tipo chamado Ord, no qual atua o mesmo papel da interface Comparable em Java e padroniza as implementaes dos trs predicados em termos de um quarto abstrato. Os predicados para igualdade e diferena no aparecem aqui pelo motivo de que so padres presentes em todos os objetos. O tipo Any que usado acima, um super-tipo de todos os outros tipos em Scala. Ele pode ser visto como uma verso mais geral do tipo Object em Java, mas tambm um super-tipo dos tipos bsicos como Int, Float, etc. Para fazer objetos de uma classe serem comparveis, mais do que suciente apenas denir os predicados que testam igualdade e inferioridade, e ento misturar o cdigo de Ord classe. Como um exemplo, deniremos uma classe Date, representando datas no calendrio gregoriano. As datas so compostas por um dia, um ms e um ano e so representados como inteiros. Comearemos a denio da classe Date como o seguinte:
class def def def Date(y: Int, m: Int, d: Int) extends Ord { year = y month = m day = d
A parte importante aqui a declarao de extends Ord que segue do nome da classe e dos parmetros. Isto declara que a classe Date uma sub-classe da classe Ord como mixin. Ns redenimos o mtodo equals que foi herdado de Object, ento ele comparar corretamente as datas, comparando seus campos individualmente. A implementao padro de equals no deve ser usada, pois como em Java ele compara os mtodos sicamente. Ns chegamos na seguinte denio:
override def equals(that: Any): Boolean = that.isInstanceOf[Date] && { val o = that.asInstanceOf[Date] o.day == day && o.month == month && o.year == year }
Este mtodo faz o uso dos mtodos j predenidos isInstanceOf e asInstanceOf. O primeiro, isInstanceOf, corresponde ao operador instanceof do Java e retorna verdadeiro se, e apenas se, o objeto no qual foi aplicado, uma instncia do tipo dado. O segundo, asInstanceOf, corresponde ao operador de cast do Java: se o objeto uma instncia do tipo dado, ele visto como tal, seno uma exceo ClassCastException lanada. Finalmente, o ltimo mtodo para denir o predicado no qual testa a inferioridade, como a seguir. Este faz uso de outro mtodo pr-denido, error, que lana uma exceo junto a dada mensagem de erro.
14
def <(that: Any): Boolean = { if (!that.isInstanceOf[Date]) error("cannot compare " + that + " and a Date") val o = that.asInstanceOf[Date] (year < o.year) || (year == o.year && (month < o.month || (month == o.month && day < o.day))) }
Assim completamos a denio da classe Date. Instncias dessa classe podem ser vistas como datas ou como objetos comparveis. Alm do mais, elas todas denem os seis predicados de comparao mencionados acima: equals e <, pois eles aparecem diretamente na denio da classe Date, e os outros por causa da herana do mixin Ord. Mixins so teis em outras situaes a mais do que as mostradas aqui, com certeza, mas discutir todas as suas aplicaes est fora do escopo deste documento.
Generalizao
A ltima caracterstica de Scala que exploraremos neste tutorial a generalizao. Programadores Java devem estar bem prevenidos sobre os problemas causados pela falta de generalizao em sua linguagem, uma decincia que abordada no Java 1.5. Generalizao a habilidade de escrever cdigo parametrizado por tipos. Como exemplo, um programador escrevendo uma biblioteca para listas encadeadas encontra o problema de decidir qual tipo dar para os elementos da lista. Desde que esta lista foi concebida para ser usada em diferentes contextos, no possvel decidir que o tipo dos elementos tero de ser, por exemplo, Int. Isto pode ser totalmente arbitrrio e muito restritivo. Programadores Java contornam isto usando Object, que o super-tipo de todos os objetos. Contudo, esta soluo est longe da ideal, desde que no funcionar para os tipos bsicos (Int, Long, Float, etc.) e implicando que vrios type casts tero de ser inseridos pelo programador. Scala torna possvel denir classes (e mtodos) genricos para resolver este problema. Vamos examinar isto com um exemplo do container de classe mais simples possvel: uma referncia na qual pode ser vazia ou apontar para um objeto de algum tipo.
class Reference[a] { private var contents: a = _ def set(value: a) { contents = value }
9 Concluso
15
A classe Reference parametrizada por um tipo, chamado a, que o tipo do seu elemento. Este tipo usado no corpo da classe como o tipo da varivel contents, do argumento do mtodo set e do tipo de retorno do mtodo get. O exemplo do cdigo acima mostra variveis em Scala que no precisamos explicar mais. Contudo interessante ver qua o valor dado inicialmente para a varivel _, que representa o valor padro. Este valor padro 0 para os tipos numricos, false para o tipo booleano, () para o tipo unit e null para todos os tipos de objetos. Para usar esta classe Reference, alguem precisa especicar qual o tipo para se usar no parmetro de tipo a no qual o tipo contido pela clula. Como exemplo, ao criar e usar uma clula guardando um inteiro, algum deve escrever assim:
object IntegerReference { def main(args: Array[String]) { val cell = new Reference[Int] cell.set(13) println("Reference contains the half of " + (cell.get * 2)) } }
Como pode ser visto no exemplo, no necessrio fazer cast no valor retornado pelo mtodo get antes de us-lo como um inteiro. Tambm no ser possvel guardar nada alm de um inteiro naquela clula particularmente, pois foi declarada para guardar um inteiro.
Concluso
Este documento apresenta uma rpida viso geral da linguagem Scala com alguns poucos exemplos. Caso deseje aprofundar-se mais recomendamos que leia o Scala By Example, que possui muito mais exemplos, e consulte a Scala Language Specication quando necessrio.
10
Sobre a traduo
Este documento foi traduzido por Marcelo Castellani e Thiago Rocha, membros da lista de discusso Scala-Br. Para participar visite a pgina a seguir:
http://groups.google.com/group/scala-br