Programação, Scala

Aprenda Linguagem Scala – Parte II – Sintaxe

Como vimos em nossa introdução à linguagem Scala, a sintaxe de Scala, assim como Java, deriva da linguagem C. Porém, Scala traz diversas diferenças desta, a exemplo dos parenteses e colchetes intercambiáveis em certos casos(citado no Tutorial Parte I).

Neste artigo veremos alguns detalhes adicionais da sintaxe Scala. Recomendamos que o leitor tenha o Scala IDE aberto com uma Scala Worksheet criada para copiar os exemplos oferecidos.

Strings em múltiplas linhas
Em Scala, é possível criar cadeias de caracteres de múltiplas linhas usando o delimitador """(aspas triplas):

val exilio = """
Minha terra tem palmeiras,
Onde canta o Sabiá;
As aves, que aqui gorjeiam,
Não gorjeiam como lá.
"""
println(exilio)
> Minha terra tem palmeiras,
> Onde canta o Sabiá;
> As aves, que aqui gorjeiam,
> Não gorjeiam como lá.

Valores de retorno
Em Scala existe a palavra-chave return. No entanto, como todas as expressões da linguagem possuem um valor de retorno, muitas vezes o uso explícito deste termo é redundante. Por exemplo:

val dez = 10
def vinte = dez * 2

Note que não há um return explícito antes de dez * 2, o valor da expressão é simplesmente atribuído ao nome da função, no caso vinte. Esta característica é oriunda do Cálculo de Lambda, o qual é a base da programação funcional. Dizemos que a expressão dez * 2 foi “ligada” a vinte. Esta maneira de enxergar a programação deve ser praticada de modo a usar eficientemente as linguagens funcionais: a distinção entre código e dados não existe nas linguagens funcionais. Funções e valores primitivos são a mesma coisa, apenas tem sintaxes distintas. Valores literais apenas retornamm sua própria valoração, e funções podem substituir valores literais antes de retornarem uma outra transformação ou um valor literal. No entanto, para nos acostumarmos ao paradigma funcional, funções e valores literais devem ser vistos como o mesmo conceito.

Para aprender a programar no paradigma funcional, devemos muitas vezes “nos esquecermos” de conceitos adquiridos através das linguagens imperativas. Este fato se tornará mais claro conforme o programador pratique os idiomas funcionais.

No exemplo acima, vinte é uma função, a qual é definida como um valor primitivo ligado à expressão dez * 2. Toda vez que vinte() for encontrado no código, a função será chamada e retornará o valor da expressão dez * 2. Já o valor dez será avaliado apenas uma vez. A principal diferença de val para def é apenas a forma como o valor é avaliado. def promove a avaliação sempre que a função for chamada, e val é avaliada apenas uma vez.

lazy val
Haskell é, possivelmente, a única linguagem 100% funcional. Haskell não possui orientação a objetos, e não traz quaisquer adaptações para permitir programação imperativa(diferente de Scala). Essa característica torna possível avaliar valores apenas quando são usados. Como bem sabemos, linguagens funcionais não permitem valores mutáveis. Se os valores não são mutáveis, a ordem em que são avaliados não importa! Desde que o valor se encontre definido no momento em que será usado, não haverão problemas. Haskell usa, portanto, a avaliação retardada como padrão: valores só são avaliados no último instante, imediatamente antes de serem requisitados.

Esta é uma visão extrema dos dados imutáveis, e Scala possui um modelo mais conciliatório entre os mundos funcional e orientado a objetos. Scala permite que tal funcionalidade seja usada quando desejada, portanto o modo de avaliação padrão de Scala é imediato : os valores são definidos tão logo sejam encontrados no código. Para ativar a avaliação retardada devemos adicionar a palavra-chave lazy à definição dos valores.

lazy val conexaoBancoDados = DB.forConnection(testandoConexao())

conexaoBancoDados no exemplo acima pode ser usado no código, porém só será avaliado quando seu valor for requisitado. Assim, podemos retardar as conexões ao banco de dados até o último instante, quando serão realmente requisitados dados a partir desta conexão.

Importando código
Em Java, usamos o comando import para trazer módulos externos ao escopo do atual programa. Em Scala usamos o mesmo termo, porém com sintaxe modificada. No lugar de *, usamos _ para representar o “coringa” ou “wildcard”.

// Importa todos os módulos sob java.io
import java.io._
// Importa dois módulos de scala.sys.process
import scala.sys.process.{FileProcessLogger, BasicIO}

Scala trata funções como objetos(falaremos mais sobre o assunto em outro tutorial), logo funções podem ser importadas diretamente de módulos, assim como em Java temos os static imports:

import scala.math.BigInt.int2bigInt

A partir desta linha a função int2bigInt encontra-se no escopo do atual trecho de código e pode ser usada sem prefixo, como se fosse definida localmente. Esta é a idéia do static import de Java.

Devido à sua natureza funcional, Scala possui uma grande base de código implementada de forma estática, através de funções que podem ser chamadas diretamente dos módulos sem instanciação. Este paradigma é diferente de Java onde a grande maioria do código é implementado em métodos de classe onde a classe deve ser instanciada para que se torne útil.

Tudo em Scala é um objeto!
Uma das faces mais marcantes da linguagem Ruby é o fato de absolutamente tudo naquela linguagem ser um objeto, característica que Scala também adotou como padrão. Dessa forma, tipos “primitivos” como inteiros e doubles podem ter métodos ligados a eles. Sabemos que Scala é estaticamente e fortemente tipada. Não existem tipos “gerais” como em Perl e Python, onde podemos atribuir uma cadeia de caracteres a uma variável e logo em seguida atribuir-lhe um inteiro. As variáveis de Scala tem um único tipo definido no momento da compilação(daí a origem do termo “tipagem estática”, pois ocorre antes do programa “se mover”). Assim como em Ruby, Scala não tem tipos primitivos: tudo é um objeto:

val str10 = 10.toString()
println(str10)
val maiusculas = "um pequeno texto sem maiusculas".toUpperCase()
println(maiusculas)

Chamar métodos diretamente nos valores primitivos, conforme o exemplo acima, pode parecer estranho à primeira vista. Porém, em Scala, este tipo de idioma é de praxe, e o programador deve acostumar-se com tal fato para compreender melhor o código fonte das bibliotecas padrão desta linguagem.

Parenteses vs Chaves {}
O uso de parênteses e chaves é muitas vezes intercambiável. Onde devemos usar qual? O principal a saber é que listas são definidas por parenteses, e blocos ou mera separação lógica de trechos de código é feita por chaves.

val minhaLista = List[String]("um", "dois", "tres")
val outraLista = List[String]{"um", "dois", "tres"} // errado!

Na segunda linha há um erro de sintaxe. As chaves não permitem a listagem de itens! Os parenteses dão origem a listas de Scala, nativamente chamadas “tuplas”. Já as chaves permitem apenas uma instrução, ou quando delimitadas por ; podem ser usadas sequências de instruções, porém não uma lista.

val outraLista = List[String]{"um"} // ok!

Neste exemplo, a lista outraLista é definida com apenas um ítem, assim as chaves foram usadas apenas como delimitadoras de código. Vejamos um exemplo usando a expressão for:

val minhaLista = List[String]("um", "dois", "tres")
val terceiraLista = for {a <- minhaLista; b <- a.toUpperCase() } yield b

Neste exemplo, vemos que há mais de uma expressão dentro de for. Porém, esta sequência de expressões não é uma lista, e sim um bloco de código, onde as instruções foram separadas por ;. Neste contexto, o “bloco de código” é um conceito familiar oriundo de C, porém devemos observar que funções de um só parâmetro, listas de um ítem só, podem ser definidas com {}’s também, o que não é possível em C ou Java. Este é um detalhe que pode causar certa confusão para iniciantes Scala.

Construtores
Em Scala, construtores são basicamente trechos de código soltos dentro de classes. Ao instanciar classes, aquele código deve ser executado. Assim, o conceito de construtor é relativamente intuitivo em Scala, códigos soltos dentro de classes são executados no momento de sua instanciação:
class Automovel(modelo: String) {
println("Fui instanciado. Sou um carro modelo " + modelo + "\n")
}
object ScalaTutorialLiterati {
def main(args: Array[String]) = {
val fusca = new Automovel("Fusquinha Itamar Franco")
}
}

Que produz a saída: Fui instanciado. Sou um carro modelo Fusquinha Itamar Franco

Note também que os parâmetros para o construtor foram definidos juntamente com a classe. Não há um método com mesmo nome da classe, a própria definição da classe já indica qual o construtor principal. O construtor principal é o único construtor de fato da classe. Para criar outras assinaturas de construtores, estes devem chamar o construtor principal:
class Automovel(modelo: String) {
println("Fui instanciado. Sou um carro modelo " + modelo + "\n")
def this() = {
this("Carro Padrao")
}
}

Nesta segunda versão da classe Automovel, criamos um construtor que não recebe parâmetros. Assim, podemos instanciar um Automovel padrão usando new Automovel(). Repare que o construtor se chama this e que a primeira chamada é ao construtor principal, o qual recebe uma String como único parâmetro.

Idioma: Encadeamento de métodos
Sabemos que tudo em Scala é um método, e que praticamente todas as construções da linguagem retornam um valor. Esta combinação favorece um estilo de programação onde o objeto sobre o qual um método foi chamado é retornado pelo método, de modo que diversas chamadas podem ser encadeadas.

// veremos case classes posteriormente
case class Usuario(nome: String, idade: Int) {
def nomeMaiusculas = {
nome toUpperCase;
this
}
def nomeMinusculas = {
nome toLowerCase;
this
}
}
object ScalaTutorialLiterati {
def main(args: Array[String]) = {
val usuario: Usuario = Usuario("Joao Martinez", 61)
val minusculas = usuario.nomeMaiusculas.nomeMinusculas
println(minusculas)
}
}

No exemplo acima, cada método da classe Usuario retorna sua própria instância, de modo que vários métodos de Usuário podem ser encadeados. Este é um idioma típico de linguagens funcionais, o qual será muito encontrado em Scala. Não é parte da sintaxe da linguagem em si, mas achamos que deveria ser mencionado visto que pode auxiliar na leitura de código de terceiros.

Conclusão
Neste segundo passeio introdutório pela linguagem Scala, vimos algumas particularidades de sua sintaxe, a qual é baseada em C mas, como pode ser notado, traz diversas inovações que visam deixar o código mais legível, com menos trechos repetitivos e pontuações e “interrupções” desnecessárias.

Standard