Programação, Scala

O valor retornado pela atribuição em Scala

Resumo
Em Scala, o comando de atribuição não retorna o valor atribuído. O valor de t na expressão val t = (outraVar = 10) será Unit, e não o inteiro 10. Por este motivo loops em Scala não podem ser condições de continuação escritas assim: while ( (algumReader.readLine()) != null ) { /* ... */ }.

O paradigma de C
Programadores de linguagem C estão acostumados a utilizar este idioma:

#define QTOS 1024
int i;
while ( (i = fread(destino, 1, QTOS, stdin)) == QTOS ) {
// realiza alguma tarefa com destino
}
// destino terá os ultimos dados lidos

A atribuição do retorno de fread ao inteiro i ocorre dentro da condição de continuação do loop while.

Note que i = fread(destino, 1, QTOS, stdin) é uma expressão: é uma instrução que atribui ao lado esquerdo(lvalue) o valor resultante da expressão do lado direito(rvalue). Mas a atribuição em sí é uma expressão, logo há um valor de retorno. Qual este valor?

Em C, Java, C++, Perl e outras derivadas de C, o valor de retorno da atribuição é o próprio valor atribuido. Em outras palavras, a expressão i = fread(destino, 1, QTOS, stdin) atribui a i o valor retornado por fread, e logo retorna este mesmo valor.

No caso do loop while o retorno da expressão de atribuição é comparado a um valor de controle, decidindo continuar ou não a execução do corpo do loop.

O valor de retorno dos loops em Scala
Um dos aspectos mais interessantes da linguagem Scala é o fato de todas as construções da linguagem retornarem algum valor. Para aqueles que começam a jornada de aprendizado da linguagem Scala, uma das primeiras surpresas é código semelhante a este:

val numero = 2;
val algoAqui = if ((numero % 2) == 0) algo else outracoisa

if, em Scala, é uma expressão, não apenas uma estrutura de controle. Possui as mesmas regras lógicas de C: o primeiro bloco só é executado em caso de expressão verdadeira, e o segundo caso contrário. Porém, cada bloco pode retornar um valor diferente do mesmo tipo, assim uma variável pode receber o retorno de if, o que não ocorre em C, Java, Perl e C++.

Por exemplo, o que programadores C chamam de “loop for”, os programadores de Scala podem denominar loop ou expressão for, visto que os loops for em Scala podem retornar valores. A expressão for em Scala é uma das construções mais poderosas da linguagem, permitindo executar computações complexas em coleções de dados e posteriormente retornar uma nova coleção de dados processados. Já o loop for de Scala funciona de forma semelhante a C e Java e não tem um valor de retorno.

Os loops for () {}(não confundir com a expressão for () yield), while e do ... while da linguagem Scala não retornam um valor usável. Para não quebrar o paradigma de retornar um valor para toda instrução, estes loops retornam o valor Unit, equivalente ao void de C. Em Scala, o valor Unit pode também ser representado por ().

O valor de retorno da atribuição em Scala
Enfim, chegamos ao assunto desta postagem. Analise o código a seguir:

val br = new BufferedReader(new InputStreamReader(new BufferedInputStream(entrada)))
var ln: String = ""
val sb: StringBuilder = new StringBuilder()
while ((ln = br.readLine()) != null) {
sb ++= ln
}
println(sb.toString)

Se o leitor executar o código acima, notará que o println após o loop jamais será executado pois a condição do while jamais será falsificada. Qual o erro?!

O problema está no valor de retorno da atribuição ln = br.readLine(). Em Scala, atribuições sempre retornam Unit. Em uma linguagem mista, com viés funcional, pode ser difícil explicar o fato de atribuições não retornarem o valor atribuído. De fato, a explicação não é tão simples.

Segundo o criador da linguagem, a atribuição em Scala geraria operações desnecessárias, armazenando valores que “em 95% dos casos não serão utilizados”. Questionado por que o compilador não detecta quando o retorno não seria usado, a explicação é que em métodos de atribuição (setters) tais expressões retornariam o valor e o compilador seria forçado a verificar todos os efeitos colaterais dessas expressões.

Exemplo, em uma classe que tenha o campo minhaIdade:

def setIdade(i: Int) = minhaIdade = i

O método setIdade normalmente deve retornar Unit(ou void em Java), no entanto na expressão acima ele retornaria um inteiro. Em Perl, Java e C, o valor de retorno é automaticamente descartado quando não é usado, ou seja, os métodos setters podem simplesmente “esquecer” o valor. Mas em Scala toda expressão tem um valor de retorno, e este valor é rigidamente checado quanto a seu tipo. Logo o compilador não pode descartar o retorno da atribuição do método setIdade acima, e o método setter teria valor de retorno Int e não Unit. O compilador então teria que verificar todos os retornos de métodos do tipo setter, o que geraria uma cadeia imensa de verificações especialmente em sistemas de grande porte.

A decisão de Martin Odersky e David Pollak foi de retirar o valor de retorno de atribuições, dando origem a este “pega” da linguagem Scala.

Realmente é de se estranhar o fato de uma linguagem que busca proporcionar um valor usável para todas as expressões descartar o valor de retorno logo da mais simples das expressões.

Standard