Press enter to see results or esc to cancel.

Introdução a linguagem Scala – Parte 1

A Máquina Virtual Java (JVM) é muito conhecida por sua portabilidade, possibilitando o desenvolvimento de aplicações que possam rodar em diversas plataformas, o famoso “write once, run everywhere”. Isso é possível por que quando um código em Java é compilado é gerado um código intermediário formado por bytecodes, que são instruções independentes de plataforma que a JVM consegue executar. Além disso a JVM ainda faz otimizações em cima desses bytecodes usando um Just In Time Compiler (JIT), ou seja, além desta portabilidade, temos uma máquina que vai rodar seu código de forma bastante otimizada em várias plataformas.

Mas você deve estar se perguntando por que estou falando de tanto de JVM e bytecodes … Bom, por conta dessa abstração é possível programar em outras linguagens para a JVM , desde que esta outra linguagem tenha um compilador que gere esses mesmo bytecodes que o compilador Java gera e este é justamente o caso da linguagem Scala e de algumas outras linguagens que podem ser vistas na imagem abaixo.

Outras linguagens que rodam na JVM
Outras linguagens que rodam na JVM

O interessante disso tudo é que a maioria das linguagens que rodam na JVM são interoperáveis, ou seja, podem compartilhar bibliotecas, já que no final das contas tudo é bytecode. Mas vamos falar de Scala agora finalmente, que é o foco deste artigo.

O que é Scala afinal ?

É uma linguagem de programação de Tipagem Forte e Estática que mescla programação Orientada a Objetos e programação Funcional. O interessante é que na linguagem tudo é objeto, ou seja, não existem tipos primitivos como em Java, até mesmo um simples número é um objeto. Veja que interessante o caso de um Int em Scala.

val numero = 1
val numeroComTipo: Int = 5
val texto = "High5Devs"

println(numero.getClass()) // Int
println(numeroComTipo getClass()) // Int
println(texto getClass) // String

numero + numeroComTipo
numero.+(numeroComTipo)

Só neste exemplo podemos ver três características interessantes em Scala :

  1. Nas duas primeiras linhas estamos declarando duas variáveis do tipo Int, porém na primeira linha foi omitida a declaração de tipos e na segunda linha foi deixado isso explicito, ou seja, isso mostra que a linguagem tem Inferência de Tipos. Por conta da inferência de tipos a variável numero é do tipo Int porque o compilador inferiu que a atribuição a ela é do tipo Int, logo não é preciso informar o tipo da variável explicitamente. Já é bem menos verboso que Java né ?
  2. Nas próximas três linhas vemos que pontos e parênteses para acessar e chamar métodos nos objetos são opcionais em alguns casos, o que faz com que a linguagem fique mais expressiva em alguns momentos. O que nós leva ao próximo ponto que é …
  3. Não existem operadores em Scala, eles são métodos dos objetos.  Como podemos ver nas últimas linhas, objetos do tipo Int possuem o método + que recebe outro Int e retornam um Int. 

Assim como Java, Scala possui as tradicionais classes, subclasses, tipos abstratos e sobrecarga de métodos. A linguagem possui uma sintaxe mais amigável para se fazer classes com construtores padrões. Vamos ver algumas diferenças dos construtores entre Java e Scala, segue os exemplos.

//Classe bola em Java
class Bola {

  Integer tamanho;

  Bola(Integer tamanho){
    this.tamanho = tamanho;
  }
}
//Classe Bola em Java
class Bola{

  //Atributo com algum valor default deve ser inicializado com "_"
  var tamanho:Int = _

  def this(tamanho:Int) = {
    this()
    this.tamanho = tamanho
  }

}
//A mesma classe Bola em Scala mas usando a sintaxe mais curta
class BolaSimples(val tamanho: Int)

Desses três exemplos vemos que podemos escrever classes de forma bem verbosa como em Java, porém em Scala temos essa forma curta de se escrever classes que tenham parâmetros padrão.  Inicialmente pode parecer confuso mas normalmente é utilizada a forma curta em códigos Scala para evitar repetições no código.

Outro ponto forte da linguagem é o incentivo para criar o máximo de classes para descrever o comportamentos dentro do seu código, já que a linguagem tem compilador bem complexo de tipos e ele deve explorado ao máximo. Ainda não vamos entrar a fundo no sistema de tipos, mas já podemos ver essa facilidade já que a linguagem fornece formas bem mais simples se criar uma classe, deixando até mesmo que sejam criação de várias classes dentro do mesmo arquivo, o que tende a deixar os códigos mais expressivos ainda.

Ainda em orientação a objetos, a linguagem também possui o conceito de Traits, que basicamente são utilizados para aplicar composições em outras classes ao invés de utilizar herança múltipla. Para exemplificar isso, nada melhor que um exemplo um pouco mais elaborado para entender tudo isso.


//Construtor padrão de animais
abstract class Animal(val nome:String) {
  def fazerBarulho:String
}
class Cachorro(nome:String) extends Animal(nome){
  override def fazerBarulho = {
    "Au Au"
  }
}

class Gato(nome:String) extends Animal(nome){
  override def fazerBarulho = {
    "Miau Miau"
  }
}

val gato = new Gato("Garfield")
val cachorro = new Cachorro("Pluto")
val animais = List(gato, cachorro)

gato.fazerBarulho //Miau Miau
cachorro.fazerBarulho // Au Au
animais.foreach { animal =>
  animal.fazerBarulho //Todos aqui tem o método fazerBarulho
}

Primeiramente criamos um exemplo básico de herança com a classe abstrata Animal que tem como parâmetro obrigatório o nome e que seus filhos devem implementar o método fazerBarulho que retorna uma String. Depois são criadas as classes Cachorro e Gato que herdam de Animal e que implementam fazerBarulho.

Após isso são instanciados os objetos das classes Cachorro e Gato e eles são colocados em um List, que por inferência é do tipo List[Animal] pois é o tipo mais genérico dos itens da lista. Logo após isso iteramos na lista de animais e seguramente sabemos que os animais devem ter o método fazerBarulho. Tudo certo até agora eu espero.

trait Asas{
  def voar = {
    "Estou voando"
  }
}

class Pato(nome: String) extends  Animal(nome) with Asas {
  override def fazerBarulho = {
    "Quack Quack"
  }
}

val gatoComAsas = new Gato("Gato morto") with Asas
val pato = new Pato("Donald")

//A inferência nos dá o tipo mais genérico que é Animal
val animaisComAsasInferencia = List(gatoComAsas, pato)
val animaisComAsasTrait = List[Asas](gatoComAsas, pato)
val animaisComAsas = List[Animal with Asas](gatoComAsas, pato)

gatoComAsas.voar //Estou voando
pato.voar //Estou voando
animaisComAsas.foreach { animal =>
  animal.voar //Todos aqui tem o método voar
}

Agora eu quero que alguns animais possam ter asas, então foi criado um Trait, que basicamente é uma interface do Java mas que pode ter implementações dos métodos e pode ser composto em outras classes. Com isso foi criada uma nova classe Pato que herda de Animal mas que também tem interesse em ter Asas, sem precisar re-implementar o método voar. Mas e se eu quiser que um animal ganhe essa habilidade de voar on the fly ? Scala me deixa fazer isso como podemos ver na declaração da variável gatoComAsas em que eu adicionei o comportamento Asas na instanciação do objeto, Cool Huh !?! O resto é a mesma coisa do exemplo anterior, a diferença é que o meu interesse ao iterar na lista é que os objetos ali tenham o comportamento de voar.

Mas e a parte funcional da linguagem você deve estar se perguntando. Em Scala nós temos Funções de Primeira Classe e Funções de Ordem Maior (First Class Functions and High Order Functions). Basicamente isso significa que funções podem ser armazenadas e passadas como parâmetro e que uma função pode retornar outras funções. Vamos aos exemplos.


val triplicar = (n:Int) => n*3 // Função anônima de triplicar um número
triplicar(3) // 9

//Função que gera função de multiplicar
// O retorno é uma função que recebe um Int e retorna um Int
def multiplicarPor(n: Int): (Int) => Int = {
  (m:Int) => m * n
}

val triplicarHighOrder = multiplicarPor(3) //Gera função que triplica
val duplicarHighOrder = multiplicarPor(2) //Gera função que duplica
triplicarHighOrder(3) // 9
duplicarHighOrder(3) // 6

Este exemplo mostra que podemos criar uma função com a sintaxe (parametros da função:TipoDoParametro): TipoDoRetorno => Corpo da Função. Veja que vários itens são opcionais nos exemplos e que deixam o código bastante expressivo:

  1. Na função triplicar o retorno foi inferido da expressão n*3 que será do tipo Int.
  2. A palavra chave return é opcional também, a última expressão de uma função é o retorno.
  3. Os abre e fecha colchetes também são opcionais em função com uma linha apenas.

A função multiplicarPor recebe um Int e retorna uma função que recebe um Int e retorna um Int !!! Parece confuso mas lembre-se que em Scala as funções são Cidadãos de Primeira Classe (First Class Citizen), então esta função vai retorna uma função que pode ser executada depois, sendo assim ficou genérica a função de duplicar/triplicar.


def encontraPrimeiro[A](lista: List[A], funcaoFiltro: (A) => Boolean): Option[A] = {
  for(elemento <-  lista) {
    if(funcaoFiltro(elemento)){
      return Some(elemento)
    }
  }
  None
}

val lista = List("A","B","C")
val listaSemB = List("A","Z","C")
val funcao = (x:String) => x == "B"
encontraPrimeiro(lista, funcao) // Some(B)
encontraPrimeiro(listaSemB, funcao) // None

Um exemplo mais complexo seria este da função encontraPrimeiro que tem como objetivo receber uma lista um tipo qualquer A e uma função que fale para ele como encontrar o elemento do tipo A, ou seja, deve receber como parâmetro uma função que recebe e retorna verdadeiro ou falso para tal A que for passado como parâmetro. E tem mais, essa função pode não encontrar nada na lista, então o retorno é uma Option[A], que é uma convenção em Scala para eliminar a confusão de valores nulos, ou seja, o método pode retornar algo do tipo  ou retornar nada. Acho bem interessante esse padrão de evitar o valor nulo, que gera confusão em muitas linguagens.

Ambiente de Desenvolvimento

Eu recomendo fortemente utilizar a IDE da Jetbrains IntelliJ com o plugin de Scala, que tem suporte até mesmo na versão Community. Porém também temos a Scala-IDE que é uma IDE baseada no Eclipse. Essas IDEs já vem com o ambiente praticamente todo configurado, a única coisa necessária inicialmente é ter o Java instalado na máquina que for utilizada para desenvolvimento. Segue links para download das IDEs :

Resumindo um pouco

  • A JVM é mais que apenas Java, é uma plataforma que pode ter suporte a várias linguagens
  • Scala é Orientada a Objetos e Funcional que roda na JVM
  • É uma linguagem de Tipagem Forte e Estática
  • Tudo é um objeto
  • Códigos Scala tendem a ser mais expressivos e menos verbosos que em Java

Ainda tem alguns pontos que quero esclarecer ainda na introdução da linguagem Scala então vou criar pelo menos mais um post sobre isso, mas se não quiser esperar, segue alguns links sobre a linguagem:

Até o próximo post.

Share on FacebookTweet about this on TwitterShare on Google+Share on LinkedInEmail this to someone
Comments

1 Comment

Dhyego Fernando

As features de scala (tudo é objeto, operadores são métodos, traits podemos lembrar de modules, lambdas e blocks entre outros) me lembrou muito mesmo ruby…
O mais interessante de scala é justamente esse poder de escrever menos com as features de orientação com tipagem forte e também programação funcional.
Ótimo post, parabéns Alvaro.


Leave a Comment