Scala Arthur Felipe Bruno Pimentel Carlos Junior Mateus Araujo Roteiro • • • • • Introdução e História da Linguagem Sintaxe Aplicações e futuro Conclusão Referências Introdução e História da Linguagem • O nome Scala significa “linguagem escalável” • Projetada para integrar linguagem orientada a objetos e programação funcional • Executado em plataforma Java • Mesmo modelo de compilação como Java e C# Introdução e História da Linguagem • Recursos orientados a objetos Cada valor é um objeto, onde seus comportamentos são descritos por classes o Abstrações de classes o • Programação Funcional o o o o Definição de funções Funções de ordem superior Casamento de padrões Tipos algébricos Introdução e História da Linguagem • O design começou em 2001 na École Polytechnique Fédérale de Lausanne (EPFL) por Martin Odersky • Foi liberado no fim de 2003 e início de 2004 na plataforma Java • Em Junho de 2004 na plataforma .NET Introdução e História da Linguagem • Em 2009, Twitter mudou grande boa parte dos seus back-end do Ruby para Scala e pretende mudar o resto • A Wattzon afirmou que toda a sua plataforma foi escrita em Scala Por quê utilizar Scala? Scala é Escalável • Adequada para tarefas de qualquer tamanho o Muito pequenas (scripts) o Muito grandes (frameworks) Scala é Multiplataforma • Roda em vários ambientes o Java Virtual Machine (JVM) o Plataforma .NET o Java Platform, Micro Edition (Java ME) Scala é Elegante • Linguagem simples que une conceitos de OO e funcional o Facilita a manutenção dos programas o Usa bibliotecas ao invés de outras linguagens para desenvolver módulos específicos o Simplicidade de funcional + poder de Objetos Sintaxe Variáveis e Valores > var a = "Hello " a: java.lang.String = Hello > a = a + "World!" a: java.lang.String = Hello World! > println(a) Hello World! Variáveis e Valores > val b = "Bob Esponja" b: java.lang.String = Bob Esponja > b += " e Patrick" error: reassignment to val b += " e Patrick" ^ Inferência de tipos > val t = 1 / 2 t: Int = 0 > val d: Double = 1 / 2 f: Double = 0.0 > val g : Double = 1.0 / 2.0 g: Double = 0.5 Funções > def scale = 5 //Uma função constante scale: Int > def square(x: Double) = x * x square: (Double)Double > square(2) unnamed0: Double = 4.0 > def sumOfSquares(x: Double, y: Double) = square(x) + > square(y) sumOfSquares: (Double,Double)Double Funções > def min (x:Int, y:Int) = { > if (x < y) x > else y > } min: (Int,Int)Int > def invert (x: Int) = -x invert: (Int)Int > def invert (x: Int) : Int = { return -x } invert: (Int)Int Tudo é um objeto Não existem primitivas. > 1233.hashCode res0: Int = 1233 Toda operação é uma chamada de uma função: > 2 + 2 == 2.+(2) res4: Boolean = true > Console.println("Ronaldo!") Ronaldo! > Console println "Brilha muito no Corinthians" ... Estruturas de controle Praticamente todas as estruturas de controle são também expressões: > val x = if (0 < 1) 10 else 20 x: Int = 10 > val y = if (0 < 1) 10 //sem o else y: Unit = () //Unit é o 'Null' em Scala Estruturas de controle Compreensões utilizando 'for' > val places = List("Olinda", "Recife", "São Paulo", "Manaus") > for (place <- places) > println(place) Olinda \n Recife \n São Paulo \n Manaus \n > val places2 = List("Olinda", "Recife", "São Paulo", "Manaus","São Vicente") > for (place <- places2 if place.contains("São")) > println(place) São Paulo \n São Vicente \n Estruturas de controle Utilizando `yield` para passagem de valores: val dogBreeds = List("Doberman", "Yorkshire Terrier", "Dachshund", "Scottish Terrier", "Great Dane", "Portuguese Water Dog") val filteredBreeds = for { breed <- dogBreeds if breed.contains("Terrier") if !breed.startsWith("Yorkshire") } yield breed Estruturas de controle Utilizando ranges. > for (i <-1 to 10) println(i) 1 \n 2 \n ... Utilizando `while` > var count = 0 > while (count < 10) { > count += 1 > println(count) >} Funções como objetos Somatório: > def sum(f: Int => Int, a: Int, b: Int): Int = > if (a > b) 0 else f(a) + sum(f, a + 1, b) sum: ((Int) => Int,Int,Int)Int Utilizando uma função anônima: > def sumSquares(a:Int, b:Int):Int = sum(((x:Int) => x*x),a,b) sumSquares: (Int,Int)Int > sumSquares(1,10) res1: Int = 385 Funções como objetos Currying: > def filter(xs: List[Int], p: Int => Boolean): List[Int] = > if (xs.isEmpty) xs > else if (p(xs.head)) xs.head :: filter(xs.tail, p) > else filter(xs.tail, p) filter: (List[Int],(Int) => Boolean)List[Int] > def modN(n: Int)(x: Int) = ((x % n) == 0) modN: (Int)(Int)Boolean > filter(List(1,2,3,4,5,6,7,8,9,0),modN(2)) List[Int] = List(2, 4, 6, 8, 0) Exemplo: Quicksort (funcional) def sort(xs: Array[Int]): Array[Int] = { if (xs.length <= 1) xs else { val pivot = xs(xs.length / 2) Array.concat( sort(xs filter (pivot >)), xs filter (pivot ==), sort(xs filter (pivot <))) } } Para testar: > sort(Array(15,3,44,457,18)) Exemplo: Quicksort (imperativo) def sort(xs: Array[Int]) { def swap(i: Int, j: Int) { val t = xs(i); xs(i) = xs(j); xs(j) = t } def particionar(l: Int, r: Int) {...} particionar(0, xs.length - 1) } Para testar: > sort(Array(15,3,44,457,18)) Exemplo: Quicksort (imperativo) def particionar(l: Int, r: Int) { val pivot = xs((l + r) / 2) var i = l; var j = r while (i <= j) { while (xs(i) < pivot) i += 1 while (xs(j) > pivot) j -= 1 if (i <= j) { swap(i, j) i += 1 j -= 1 } } if (l < j) particionar(l, j) if (j < r) particionar(i, r) } Pattern Matching > def matchTest(x: Int): String = x match { > case 1 => "one" > case 2 => "two" > case _ => "many" > } matchTest: (Int)String > println(matchTest(3)) many Pattern Matching > val willWork = List(1, 3, 23, 90) > val willNotWork = List(4, 18, 52) > val empty = List() > for (l <- List(willWork, willNotWork, empty)) { > l match { > case List(_, 3, _, _) => println("Four elements, with the > 2nd being '3'.") > case List(_*) => println("Any other list with 0 or more > elements.") > } >} Listas > val l1 = List(1, 2, 3) > val l2: List[Int] = 4 :: 5 :: 6 :: Nil > val l3 = l1 ::: l2 l1: List[Int] = List(1, 2, 3) l2: List[Int] = List(4, 5, 6) l3: List[Int] = List(1, 2, 3, 4, 5, 6) > l3.foreach (n => println(n)) 1 \n 2 \n ... Listas Alguns métodos para se trabalhar com listas: val lista = "Will" :: "fill" :: "until" :: Nil lista(2) //retorna o segundo elemento da lista lista.count(s => s.length == 4) lista.drop(2) //lista sem os 2 primeiros elementos lista.exists(s => s == "until") lista.filter(s => s.length == 4) lista.map(s => s + "y") lista.isEmpty Tuplas > def tupleator(x1: Any, x2: Any, x3: Any) = (x1, x2, x3) > val t = tupleator("Hello", 1, 2.3) > println( "Print the whole tuple: " + t ) > println( "Print the first item: " + t._1 ) > println( "Print the second item: " + t._2 ) > println( "Print the third item: " + t._3 ) > val (t1, t2, t3) = tupleator("World", '!', 0x22) > println( t1 + " " + t2 + " " + t3 ) Mapas > val stateCapitals = Map("PE" -> "Recife", "AL" -> "Maceió") stateCapitals: scala.collection.immutable (...) > println("Imprime os objetos dentro de Options") > println( "PE: " + stateCapitals.get("PE") ) > println( "SP: " + stateCapitals.get("SP") ) "Imprime os objetos dentro de Options" PE: Some(Recife) SP: None Mapas > println( "Retirando o objeto da Option...") > println( "PE: " + stateCapitals.get("PE").get ) > println( "SP: " + > stateCapitals.get("SP").getOrElse("Oops2!") ) Retirando o objeto da Option... PE: Recife SP: Oops2! Arrays > val names = Array("José","Maria") > names(0) = "João" > val cities = new Array[String](2) > cities(0) = "Recife" > cities(1) = "Olinda" > cities.foreach{println} Classes e Objetos • Em Scala todo valor é um objeto. • Tipos e comportamentos de objetos são descritos por classes e traits. • Todos os valores são instâncias de classes. Hierarquia de Classes Classes e Objetos Classes são templates estáticos que podem ser instanciados em vários objetos durante o tempo de execução. class Point(xc: Int, yc: Int) { var x: Int = xc var y: Int = yc def move(dx: Int, dy: Int) { x = x + dx y = y + dy } override def toString(): String = "(" + x + ", " + y + ")"; } Classes e Objetos Object: classe com uma única instância(singleton). Classes são instanciadas com a primitiva new. object Classes { def main(args: Array[String]) { val pt = new Point(1, 2) val pt2 = new Point(3,4) println(pt, pt2) } } Classes e Objetos • Classes podem extender outras classes. • Métodos podem ser sobrescritos. class ColorPoint(u: Int, v: Int, c: String) extends Point(u, v) { val color: String = c def compareWith(pt: ColorPoint): Boolean = (pt.x == x) && (pt.y == y) && (pt.color == color) override def move(dx: Int, dy: Int): ColorPoint = new ColorPoint(x + dy, y + dy, color) } Classes e Objetos Adicionando algumas restrições em classes: class Person(name: String, age: Int) { if (age < 0) throw new IllegalArgumentException def sayHello() { println("Hello, " + name) } } class Adult(name: String, age: Int) { require (age >= 18) def sayHello() { println("Já pode tomar cana, " + name) } } Case Classes São classes que exportam seus parâmetros de construtor. Fornecem um mecanismo de decomposição recursiva através de pattern matching. abstract class Term case class Var(name: String) extends Term case class Fun(arg: String, body: Term) extends Term case class App(f: Term, v: Term) extends Term Case Classes object TermTest extends Application { def printTerm(term: Term) { term match { case Var(n) => print(n) case Fun(x, b) => print("^" + x + ".") printTerm(b) case App(f, v) => Console.print("(") printTerm(f) print(" ") printTerm(v) print(")") } } Traits Similares a interface em java. Permitem implementação parcial. trait Similaridade { def eSimilar(x:Any) : Boolean def naoSimilar(x:Any) : Boolean = !eSimilar(x) } Traits O metodo "naoSimilar" não precisa ser implementado pelas classes que extenderem o trait Similaridade. Exemplo: class Ponto(xc: Int, yc: Int) extends Similaridade { var x: Int = xc var y: Int = yc def eSimilar(obj: Any) = obj.isInstanceOf[Ponto] && obj.asInstanceOf[Ponto].x == x } Composição Mixin Especie de herança multipla. Herda os métodos da superclasse + os novos métodos das classes mixins. Mixin: classe que aparece associada á palavra with na declaração de classes. Apenas trait pode ser usada como mixin. Composição Mixin abstract class AbsIterator { type T def hasNext: Boolean def next: T } trait RichIterator extends AbsIterator { def foreach(f: T => Unit) { while (hasNext) f(next) } } class StringIterator(s: String) extends AbsIterator { type T = Char private var i = 0 def hasNext = i < s.length() def next = { val ch = s charAt i; i += 1; ch } } object StringIteratorTest { def main(args: Array[String]) { class Iter extends StringIterator(args(0)) with RichIterator val iter = new Iter iter foreach println } } Generics class Pilha[T] { var elems: List[T] = Nil def push(x: T) { elems = x :: elems } def top: T = elems.head def pop() { elems = elems.tail } } ... val pilha = new Pilha[Int] ... Estendendo a linguagem Escalabilidade -> Extensibilidade Exemplo: Tipos numéricos • Linguagens atuais suportam int, float, double, long... • Deveriam suportar também BigInt, BigDecimal, Complex... ? • Existem boas razões para suportar todos eles, mas tornaria a linguagem muito complexa! Solução: Permitir que os usuários extendam a linguagem conforme suas necessidades! Definindo um novo tipo de dados class Rational(n: Int, d: Int) { private def gcd(x: Int, y: Int): Int = {...} private val g = gcd(n, d) val numer: Int = n/g val denom: Int = d/g def + (that: Rational) = new Rational(numer * that.denom + that.numer * denom, denom * that.denom) def - (that: Rational) = new Rational(numer * that.denom that.numer * denom, denom * that.denom) ... } Definindo novas estruturas de controle object TargetTest1 extends Application { def whileLoop(cond: => Boolean)(body: => Unit): Unit = if (cond) { body whileLoop(cond)(body) } var i = 10 whileLoop (i > 0) { println(i) i -= 1 } } Definindo novas estruturas de controle Utilizando using para controle de recursos: using (new BufferedReader(new FileReader(path))) { f => println(f.readLine()) } Ao invés de: val f = new BufferedReader(new FileReader(path)) try { println(f.readLine()) } finnaly { if (f != null) f.close() } Definindo novas estruturas de controle Implementação: def using[T <: {def close() } ] (resource: T) (block: T => Unit) { try { block(resource) } finally { if (resource != null) resource.close() } } Concorrência Concorrência • Gerenciar concorrência não é tarefa fácil em muitas linguagens o Como lidar com acesso simultâneo a memória, disco, variáveis e filas de mensagens? o Como saber o comportamento do programa com 5,10,500 processos simultâneos? o Como gerenciar locks e notifiers? Concorrência Infelizmente gerenciar concorrência em outras linguagens traz mais perguntas do que repostas. Felizmente, Scala oferece uma abordagem elegante e flexível para estes problemas.... Actors Concorrência Actors na teoria: • São objetos que recebem mensagens e tomam ações baseados nelas. • Um Actor pode enviar a mensagem para outro ou criar um novo Actor de acordo com a mensagem recebida. • Não possuem sequência ou ordem de ações, não compartilham estado global. Concorrência Actors na prática: • Objeto que herda de scala.actors.Actor • Principais métodos: Concorrência Actors na prática: import scala.actors.Actor class Redford extends Actor { def act() { println("A lot of what acting is, is paying attention.") } } val robert = new Redford robert.start Concorrência Actors na prática: import scala.actors.Actor import scala.actors.Actor._ val paulNewman = actor { println("To be an actor, you have to be a child.") } Concorrência Actors na prática: Enviando mensagem import scala.actors.Actor import scala.actors.Actor._ val fussyActor = actor { loop { receive { case s: String => println("I got a String: " + s) case i: Int => println("I got an Int: " + i.toString) case _ => println("I have no idea what I just got.") } } } fussyActor ! "hi there" fussyActor ! 23 fussyActor ! 3.33 Concorrência Actors na prática (MailBox): import scala.actors.Actor import scala.actors.Actor._ val countActor = actor { loop { react { case "how many?" => { println("I've got " + mailboxSize.toString + " messages in my mailbox.") } } } } countActor ! 1 countActor ! 2 countActor ! 3 countActor ! "how many?" countActor ! "how many?" countActor ! 4 countActor ! "how many?" Concorrência Actors na prática: • Aplicação exemplo Loja do barbeiro dorminhoco. Aplicações e futuro • Scala se encaixa no futuro multiplataforma e multilinguagem das linguagens de programação • Scala é uma linguagem fácil de aprender para programadores Java-like. • Scala oferece mecanismos flefíveis para tratar concorrência e paralelismo. Aplicações e futuro • Principais aplicações: o Kestrel Substituiu o Starling (servidor de filas utilizado pelo Twitter) Agilizou o processo de entrega de mensagens no Twitter e resolveu antigos problemas de infra do Starling o Lift Framework para criação de sistemas Web baseado em Scala. Parecido com JSF, JBoss Seam. Como é baseado em Scala resolve facilmente problemas de Java e oferece um novo ponto de vista sobre construção de sistemas Web. Conclusão • Eficiente tanto para linguagens orientados a objetos quanto funcionais • Independência de plataforma • É escalável: atende à demanda dos usuários • Linguagem de propósito geral com forma elegante, concisa e type-safe Referências • http://www.scala-lang.org/ • http://en.wikipedia.org/wiki/Scala_(programming_ language) • http://pt.wikipedia.org/wiki/Scala_(linguagem_de _programação) • Tutorial Scala - http://www.scala-lang.org/docu/ files/ScalaTutorial.pdf Dúvidas? Arthur Felipe Bruno Pimentel Carlos Junior Mateus Araujo