A Linguagem Scala (Scalabel Language) é uma linguagem de programação de propósito geral baseado em dois paradigmas: o orientado a objetos e funcional. Ela é fortemente baseada no Java, mas inclui uma série de requisitos, principalmente retirados de linguagens funcionais, para deixar o desenvolvimento de aplicações mais simples do que em Java. Scala roda na máquina virtual Java, e isso traz a vantagem de permitir o uso de classes e métodos Java em um programa Scala, e vice e versa.

Outras características da linguagem Scala são:

  • Inferência de Tipos: Em Scala não precisamos declarar explicitamente o tipo das variáveis, pois o próprio compilador infere os tipos no momento do uso delas, o que traz grande flexibilidade na programação;
  • Pattern Matching: Funciona mais ou menos como um Switch/Case, mas é muito mais poderoso, permitindo a comparação utilizando expressões regulares;
  • Traits: É um conceito de várias linguagens de programação OO, que foi adicionado em Scala também, e é mais ou menos como uma interface Java, mas que permite que os métodos sejam implementados, e não apenas declarados;
  • Funções de Alta Ordem: Em Scala funções são objetos, por isso podemos utilizá-las de muitas formas, como por exemplo, passá-las como parâmetros para outras funções.
  • Objetos: Em Scala tudo é um objeto, inclusive os tipos básicos, como Integers e Floats, e até funções.

A linguagem Scala está sendo usada em diversas empresas, como por exemplo, o Twitter, o GitHub e o FourSquare, e em diversos projetos OpenSource famosos, como por exemplo, o Apache Spark.

Instalando e Rodando o Scala

Para utilizar a linguagem Scala, é necessário ter instalado a JDK, se você utilizar uma IDE Scala, nada precisa ser feito, caso deseje desenvolver diretamente em um editor de texto e compilar os programas em console, é necessário instalar e configurar corretamente o compilador Scala, que pode ser baixado no site oficial (vid seção Links).

Exemplos da utilização de Scala

Para mostrar as principais características da linguagem Scala serão exibidos diversos exemplos de código. A Listagem 1 mostra o Hello World em Scala, onde é possível perceber que o código é bastante parecido com o Java. Veja que primeiro é definido um object, e dentro deste existe o método main, que apenas chama a função print, passando como parâmetro a String “Hello World!”. Uma diferença do Scala para o Java é que no Scala o ponto e vírgula ao final da linha não é obrigatório.

Listagem 1. HelloWorld em Scala

  package main
   
  object Main {
   
    def main(args:Array[String]) {
      print("Hello World!")
    }
  }

A Listagem 2 mostra um exemplo de declaração de variáveis em Scala. Como podemos observar, é utilizada a palavra reservada “var” para isso, e não colocamos o tipo nas variáveis, pois os tipos serão definidos pelo compilador. Qualquer tipo de variável é declarado com o var, mesmo variáveis em que os valores são passados diretamente, como por exemplo, as variáveis num1 e num2, que são do tipo inteiro, e a variável texto, que é do tipo String. Para as instâncias que são criadas com o construtor, como por exemplo, a variável texto que é do tipo StringBuffer também usamos o “var”.

Listagem 2. Declaração de variáveis em Scala

  package main
   
  object Main {
   
    def main(args:Array[String]) {   
      var num1 = 5
      var num2 = 10
      var texto = "A soma é: "
      println(texto + (num1 + num2))
      
      var s = new StringBuffer
      s.append("Olá!");
      s.append("Vamos aprender Scala?")
      println(s);
      
    }
  }

Em Scala existe também a declaração de valores fixos, ou objetos imutáveis, que são como as variáveis declaradas como final em Java. Sempre que possível, é melhor utilizar objetos imutáveis, pois eles permitem utilizar ao máximo as capacidades de execução paralela do Scala. A Listagem 3 mostra um exemplo da utilização dessas variáveis.

Listagem 3. Declaração de objetos imutáveis.

  package main
   
  object Main6 {
   
    def main(args:Array[String]) {   
      val num1 = 5
      val num2 = 10
      val texto = "A soma é: "
      println(texto + (num1 + num2))    
    }
  }

Veja que o uso é parecido com o exemplo anterior, mas a diferença é que, ao invés de var, a palavra reservada usada para essa declaração é o val. Se o programador tentar alterar o valor de um objeto declarado como val, dará um erro de compilação.

Uma das maiores diferenças do Scala em relação ao Java, é na forma em que funções podem ser usadas, pois em Scala funções são objetos e, por isso, elas podem ser utilizadas de muitas formas, como por exemplo, serem passadas como parâmetros em outras funções, permitindo uma grande flexibilidade no código. Na Listagem 4 é mostrado um exemplo onde é criada uma função chamada fazConta, que recebe como parâmetro uma função e dois parâmetros inteiros. O que o método faz é simplesmente executar a função recebida como parâmetro para os outros dois parâmetros passados para a função.

Listagem 4. Passando funções como parâmetros.

  package main
   
  object Main {
    
    def fazConta(callback: (Int,Int) => Int, x:Int, y:Int) {
        println(callback(x, y))
    }
   
    def soma(x: Int, y: Int ): Int = {
       x + y
    }
    
    def multiplicacao(x:Int, y:Int) : Int = {
      x * y
    }
    
    def divisao(x:Int, y:Int) : Int = {
      x * y
    }
       
    def subtracao(x:Int, y:Int) : Int = {
      x * y
    }
    
    def main(args: Array[String]) {
      println("Realiza as operações matemáticas:")
      fazConta(soma, 5, 3);
      fazConta(multiplicacao, 5,3);
      fazConta(divisao, 5,3);
      fazConta(subtracao, 5,3);
    }
  }

Note que a declaração de uma função em Scala tem algumas diferenças relevantes em relação ao Java. Primeiro é utilizada a palavra reservada def e depois é definido o nome da função, os parâmetros e, por fim, o tipo do retorno da função. Note também que em Scala, apesar de permitido, não é preciso colocar o return, pois é assumido que o valor do último comando da função é utilizado para retorno. Caso seja desejado que a função não retorne nada, é utilizado o tipo Unit na declaração do retorno da função.

O exemplo da Listagem 4 demonstrou uma grande vantagem da linguagem Scala, que é a passagem de funções como parâmetro, mas a quantidade de código necessário para escrever o programa não foi muito diferente do que seria necessário em Java. Porém, em Scala, é possível utilizar as funções Anônimas (atualmente o Java também permite isso com as expressões Lambda). A Listagem 5 mostra o mesmo exemplo, mas agora as funções soma e mult, ao invés de serem funções normais, são funções definidas diretamente na chamada do método fazConta e, com isso, não é necessário declarar as funções.

Listagem 5. Funções Anônimas.

  package main
   
  object Main2 {
    def fazConta(callback: (Int, Int) => Int, x: Int, y: Int) {
      println(callback(x, y))
    }
   
    def contaUmDez(f: (Int) => Unit) {
      for (i <- 1 to 10) f(i)
    }
    def main(args: Array[String]) {
   
      println("Realiza as operações matemáticas")
      fazConta((x, y) => x + y, 5, 6)
      fazConta((x, y) => x * y, 5, 3);
      fazConta((x, y) => x / y, 5, 3);
      fazConta((x, y) => x - y, 5, 3);
      
      println("Conta até 10")
      contaUmDez(i => println(i))
    }
  } 

A sintaxe para a utilização de funções anônimas é bastante simples: inicialmente são colocados os parâmetros da função. Como no exemplo, o val1 e val2, e depois o símbolo => são utilizados para as funções anônimas. Caso a função tenha apenas uma linha, basta escrever o código diretamente, caso contrário, podem ser utilizadas chaves para a criação de um bloco de código. Note que mesmo utilizando funções anônimas, a declaração do parâmetro na função fazConta e a função passada como parâmetro na chamada do método tem a mesma estrutura, número de parâmetros e tipo de retorno.

Funções anônimas são muito utilizadas para trabalhar com coleções de dados e para realizar diferentes operações nessas coleções, como por exemplo, filtragem e mapeamento dos dados. A Listagem 6 mostra funções anônimas utilizadas com coleções de dados, onde primeiro é definida a função printList, que recebe como parâmetro uma função e uma lista de inteiros. No main, a primeira coisa é a definição da lista com algumas idades, depois há duas chamadas para o método printList: uma que apenas imprime as idades, e outra que imprime as idades multiplicadas por dois.

Listagem 6. Funções anônimas com coleções de dados

  package main
   
  object Main8 {
    
    def printList(printFunction: (Int) => Unit, aList: List[Int]) : Unit = {
      for (x <- aList) {
        printFunction(x)
      }
    }  
    def main(args: Array[String]) {
      
      val listaIdade = List(5, 22, 15, 27, 7)
      println("Escreve as idades")
      printList(x => println(x), listaIdade)
      
      println("Escreve o dobro das idades")
      printList(x => println(x * 2), listaIdade)
      
      println("Filtra Idades maiores que 10")
      val listaFiltrada = listaIdade.filter(x => x > 10)
      printList(x => println(x), listaFiltrada)
   
      println("Filtra Idades não sejam menores que 10")
      val listaFiltrada2 = listaIdade.filterNot (x => x < 10)
      printList(x => println(x), listaFiltrada2)
      
    }
  }

Depois, apenas para mostrar que existem já alguns métodos implementados pela própria API da linguagem Scala, foi utilizado o método filter, que já existe no tipo list. Esse método espera como parâmetro uma função (pode ser anônima ou não), e ele vai retornar uma lista com apenas os valores que respeitem a condição passada na função como parâmetro. Apenas como exemplo, nesse mesmo código é utilizado o método finterNot, que faz exatamente o contrário do método filter, pois ele filtra apenas os elementos que não respeitam a condição passada na função.

Vejam que a função passada como parâmetro, tanto para ométodo filter como para o método filterNot, deve ser na forma Int => Boolean.

O Scala também é uma linguagem orientada a objetos, por isso, ela permite a criação de classes, com todos os conceitos de orientação a objetos, como herança, polimorfismo e associação entre classes. Para exemplificar o uso de classes em Scala, a Listagem 7 apresenta a classe Conta criada com os atributos numAgencia, numConta e saldo, e os métodos saque, deposito e toString.

Listagem 7. Definindo uma classe em Scala

  package classes
   
  class Conta(nAgencia:String, nConta:String, s:Float) {
   
    var numAgencia:String = nAgencia
    var numConta:String = nConta
    var saldo:Float = s
    
    def deposito(valor:Float): Unit = {
      saldo = saldo + valor
    }
    
    def saque(valor:Float): Boolean = {
      if (saldo - valor >= 0) {      
        saldo = saldo - valor
        return true
      }
      false
    }
    
    override def toString(): String = 
      "(Agencia:" + numAgencia + ", Conta: " + numConta + ", Saldo: " + saldo + ")";
    
  } 

A definição da classe não é muito diferente da forma que é feita em Java, pois a principal diferença é que a classe aceita parâmetros, como é possível verificar logo na linha da declaração. Esses parâmetros devem ser passados no momento da criação das instâncias da classe, substituindo o construtor. Outra diferença interessante entre Java e Scala nesse código é o método saque, que retorna um tipo Boolean, então no último comando dele basta colocar false como última instrução de um bloco, e isso automaticamente será o retorno da função. A função deposito retorna um valor do tipo Unit, que é equivalente ao void do Java, isto é, a função que retorna Unit não retorna nada.

Utilizar uma classe para criar objetos é bastante fácil em Scala, não há nenhuma grande diferença em relação ao Java. A Listagem 8 mostra um exemplo da utilização da classe Conta criada no exemplo anterior. Nesse código apenas são criados dois objetos do tipo Conta: um na variável conta e o outro na variável conta2. Verifique que conta e conta2 são declaradas como val, o que impede que sejam instanciadas novas contas nessas variáveis.

Listagem 8. Criando objetos de uma Classe Scala

  package main
   
  import classes.Conta
   
  object Main3 {
    def main(args: Array[String]) {
   
      val conta = new Conta("12343", "234543", 1000)
      println("Saque de 500 reais")
      println(conta.saque(500))
      println("Deposito de 1000 reais")
      conta.deposito(1000)    
      println(conta)
      
      val conta2 = new Conta("4567", "1234432", 1000)
      println("Saque de 1500 reais")
      println(conta2.saque(1500))
      println("Deposito de 1000 reais")
      conta2.deposito(1000)    
      println(conta2)
      
      
    }
  } 

O conceito de Pattern Matching é uma forma de fazer comparações e decisões em Scala de forma muito mais poderosa e simples do que utilizando os comandos Switch/Case. A Listagem 9 apresenta um primeiro exemplo de Pattern Matching ainda bastante simples. Nesse exemplo é passado um número inteiro como parâmetro para a função, e com a palavra reservada case, são definidas as possibilidades para o número passado como parâmetro. A última opção utiliza um underline, que seria o equivalente ao default de um comando switch/case.

Listagem 9. Pattern Matching com apenas um tipo

  package main
   
  object Main4 {
   
    def matchTest(x: Int): String = x match {
      case 1 => "um"
      case 2 => "dois"
      case 3 => "três"
      case 4 => "quatro"
      case 5 => "cinco"      
      case 6 => "seis" 
      case 7 => "sete" 
      case _ => "número passado pode ser qualquer número"
    }
   
    def main(args: Array[String]) {
      println(matchTest(3))
      println(matchTest(2))
      println(matchTest(5))
      println(matchTest(23))
    }
  }

Vejam que na definição do método matchTest, já foi declarado que ele é uma função match e que o retorno dessa função é do tipo String. Logo, os valores literais dos números ou o “qualquer número” será também o retorno da função.

No exemplo anterior é possível observar que o código é mais simples que o de um Switch/Case tradicional, mas ainda não há nenhuma grande diferença, por isso a Listagem 10 mostra um exemplo de Pattern Matching mais interessante. O parâmetro e o retorno do método são do tipo Any, o que quer dizer que pode ser passado qualquer coisa como parâmetro para o método, e que ele pode retornar qualquer coisa também. Nos cases é possível observar que existem condições que comparam valores inteiros, Strings, e até verificam o tipo da variável passada como parâmetro, já como retorno também existem inteiros, String e até um objeto da classe conta que foi criada anteriormente nesse artigo.

Listagem 10. Pattern Matching como padrões de diversos tipos

  package main
   
  import classes.Conta
   
  object Main5 {
    def matchTest(x: Any): Any = x match {
      case 1 => "um"
      case "dois" => 2
      case "três" => 3
      case "quatro" => new Conta("12345", "12345", 1000)
      case y: Int => "É um número inteiro"
      case x: Conta => "É uma conta"
      case z: String => "É uma String"
    }
    def main(args: Array[String]) {
      println(matchTest("dois"))
      println(matchTest(1))
      println(matchTest(5))
      println(matchTest("três"))
      println(matchTest("quatro"))
    }
  }

Uma consideração importante sobre o Pattern Matching é que, se nenhum padrão for encontrado para o parâmetro passado para o método, será lançada uma exceção. Por isso é importante ou ter um case com o underline, ou fazer o tratamento de exceção sempre que for chamada uma função desse tipo.

Uma grande vantagem da linguagem Scala é que ela é executada também na JVM, o que permite ao programador usar qualquer coisa que tenha sido criado em Java, inclusive frameworks e jars de terceiros. Para demonstrar isso, a Listagem 11 mostra alguns exemplos do uso de classes Java em um programa em Scala: primeiro é criado um ArrayList de Strings e são adicionados três valores nesse ArrayList; depois é criado uma instância da classe Date, e uma da classe SimpleDateFormat, e ao utilizar esses dois objetos é mostrada a data atual no console. Por último é criado um objeto da classe ServcerSocket do Java.

Listagem 11. Utilizando classes Java em um programa Scala

  package main
   
  import java.util.ArrayList
  import java.util.Date
  import java.text.SimpleDateFormat
  import java.net.ServerSocket
   
   
  object Main7 {
    def main(args: Array[String]) {
      
      val lista = new ArrayList[String]
      lista.add("Eduardo")
      lista.add("Luiz")
      lista.add("Bruna")
          
      val agora = new Date 
      val dateFormatter = new SimpleDateFormat("dd/MM/yyyy") 
      println(dateFormatter format agora)
      
      val socket = new ServerSocket(5000)
      socket.accept();
      socket.close();
      
    }
  }

Esse código não faz nada importante, mas com ele fica bem claro que é possível utilizar qualquer classe do Java em um programa Scala, para isso basta importar as classes. Isso é uma grande vantagem, pois grande parte do que é criado em Java, é possível de usar em Scala também.

Esse artigo mostrou uma introdução a Linguagem Scala, que é uma linguagem de propósito geral baseada em Java, mas que além de Orientada a Objetos, é também funcional. Foram apresentados alguns dos principais conceitos dessa linguagem e diversos exemplos de como implementá-los.

Espero que esse artigo tenha sido útil, até a próxima!

Links

Site Oficial da Linguagem Scala
http://www.scala-lang.org/

Código da Linguagem Scala no GitHub
https://github.com/scala

Download da Linguagem Scala
http://www.scala-lang.org/download/

IntelliJ para Scala
https://www.jetbrains.com/idea/features/scala.html

IDE Eclipse para Scala
http://scala-ide.org/