Então caro leitor temos muito em comum, na saga de estudar a linguagem e também o paradigma funcional, tive a oportunidade de receber a ajuda de um
Uma grande questão é que você não gostaria de reaprender tudo que já sabe sobre programação, orientação a objetos e etc. Seria interessante se tivesse uma forma de aprender reaproveitando sua experiência, focando apenas nas novidades e de forma bem prática.
Pensando nisso, começo a partir deste post, fazer uma série para programadores Java interessados em aprender Scala. Abordarei tudo de forma muito prática e bem baseada em coisas que acho importante ou código que usei para alguma solução, MUITO DO CÓDIGO SERÁ AUTO EXPLICATIVO. Explicarei a teoria e detalhes quando o código não for tão obvio assim.
Para começar sugiro usar o interpretador de Scala no terminal. Instalar, iniciar o interpretador, tudo isso é bem fácil e já existe bons materiais, portanto não serão abordados aqui, ao menos nesse primeiro momento.
Vamos começar!
Variáveis
Para declarar uma variável basta fazer:
var nomeDaVariavel = valor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var nome = "Renan" |
Note que diferente do Java não precisamos declarar o tipo da variável, isso porquê Scala consegue, muitas vezes, inferir o tipo declarado. Mas caso quisermos declarar o tipo, por questões de legibilidade por exemplo, podemos fazer:
var nomeDaVariavel : TipoDaVariavel = valor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var nome : String = "Renan" | |
//ou com nome qualificado do tipo | |
var nome : java.lang.String = "Renan" |
Também podemos criar uma variável "final" como em Java, para isso declaramos:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
val idade = 26 | |
//ou declarando o tipo | |
val idadeFinal : Int = 26 | |
// ou ainda declarando o tipo com nome qualificado | |
val idadeFinal : scala.Int = 26 |
Assim se você tentar atribuir um novo valor à variável idadeFinal receberá um erro:
error: reassignment to val
A variável "nome" é do tipo String, Scala não tem uma classe String então ela usa classe java.lang.String do Java. Já "idade" é do tipo Int (scala.Int) que é correspondente ao tipo primitivo int, que temos no Java. Scala tem tipos correspondentes para os tipos primitivos do Java, por exemplo scala.Float para float, scala.Char para char. Quando seu código é compilado em bytecodes o compilador tenta usar os tipos primitivos do Java por questões de performance.
Funções
Funções requerem uma atenção especial, pois podemos encontrar a mesma função declarada de várias formas, o que pode assustar de início, mas depois que aprendemos fica bem fácil.
Para declarar uma função:
Temos sempre de declarar o tipo dos parâmetros, a função recebe dois parâmetros do tipo Int e retorna também um tipo Int. Note que após a declaração do tipo de retorno vem um sinal de igualdade "=" e então as chaves "{}" que é onde deve ficar o corpo do método.
Em alguns casos* Scala consegue inferir o tipo do retorno da função, como é o nosso caso. Então podemos declarar a mesma função omitindo o tipo de retorno:
Como nossa função possui apenas uma linha podemos também omitir as chaves:
* Se nossa função for recursiva temos de explicitar o tipo de retorno.
Podemos declarar funções sem retorno também:
Neste caso Unit é o equivalente ao void do Java. Você pode omitir o retorno neste caso também:
Um pouquinho de características funcionais
Uma das principais características de linguagens funcionais é poder passar funções como parâmetros. Vamos a um exemplo bem prático disso, vamos criar uma classe Array e forneceremos uma maneira de executar um "processamento" em cada elemento do array.
Para isso precisamos definir nossa interface Funcao<T> que possui o método executaEm:
nomes.foreach(nome => println(nome)) // existem outras formas de escrever essa linha, abordaremos futuramente
Não se preocupe com relação ao código acima, voltaremos à essas características futuramente.
Funções
Funções requerem uma atenção especial, pois podemos encontrar a mesma função declarada de várias formas, o que pode assustar de início, mas depois que aprendemos fica bem fácil.
Para declarar uma função:
def funcao(param : TipoParam) : TipoRetorno = { // corpo }Uma função que soma dois números:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def soma(numero1 : Int, numero2 : Int) : Int = { | |
numero1 + numero2 | |
} |
Em alguns casos* Scala consegue inferir o tipo do retorno da função, como é o nosso caso. Então podemos declarar a mesma função omitindo o tipo de retorno:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def soma(numero1 : Int, numero2 : Int) = { | |
numero1 + numero2 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def soma(numero1 : Int, numero2 : Int) = numero1+ numero2 |
Podemos declarar funções sem retorno também:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def imprimeTexto() : Unit = println("Apenas um texto aqui") |
Neste caso Unit é o equivalente ao void do Java. Você pode omitir o retorno neste caso também:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def imprimeTexto() = println("Apenas um texto aqui") |
Um pouquinho de características funcionais
Uma das principais características de linguagens funcionais é poder passar funções como parâmetros. Vamos a um exemplo bem prático disso, vamos criar uma classe Array e forneceremos uma maneira de executar um "processamento" em cada elemento do array.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Array<T> { | |
List<T> elementos = new ArrayList<>(); | |
public void foreach_(Funcao<T> f) { | |
// para cada elemento chamamos o método executaEm da interface Funcao | |
for(T e : elementos) { | |
f.executaEm(e); | |
} | |
} | |
public void add(T t) { | |
elementos.add(t); | |
} | |
// outros métodos de array | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface Funcao<T> { | |
public void executaEm(T e); | |
} |
Agora podemos usar nosso Array. Vamos supor que temos um Array de String, com nomes de pessoas e queremos apenas imprimir o nome das pessoas em maiúsculo.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static void main(String[] args) { | |
Array<String> nomes = new Array<>(); | |
array.add("Renan"); | |
array.add("José"); | |
nomes.foreach_(new Funcao<String>() { | |
@Override | |
public void executaEm(String e) { | |
System.out.println(e.toUpperCase()); // ou qualquer código aqui | |
} | |
}); | |
} |
Ficou bacana. Mas repare que precisamos de uma interface classe Funcao<T> que possui apenas um método. Depois precisamos de uma implementar essa interface (no nosso caso por uma classe anônima), tudo para executar o processamento do método, podemos resumir tudo isso. Em Scala temos o recurso de funções anônimas ou "function literal" que tornam as coisas bem interessantes, o mesmo código acima pode ser escrito como:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
nomes.foreach(nome => println(nome)) // existem outras formas de escrever essa linha, abordaremos futuramente |
Imagine quantas APIs poderiam se beneficiar dessa abordagem. Lembre-se da API do AWT/Swing. Era muito improdutivo desenvolver listeners para nossos elementos de tela (botões por exemplo). Ao invés de escrever uma classe anônima, implementar um método e etc, se reproduzirmos a API em uma linguagem funcional, podemos apenas passar uma função anônima como citado anteriormente.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//recebendo uma function literal | |
var button = new Button; | |
button.addActionListener((e) => println("codigo do listener")) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//api em java | |
Button b = new Button(); | |
b.addActionListener(new ActionListener() { | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
// código do botão | |
} | |
}); |
Não se preocupe com relação ao código acima, voltaremos à essas características futuramente.