Programação, Scala

Programação imperativa em Scala

Mencionamos anteriormente que Scala tem a meta de integrar grandes sistemas usando apenas uma linguagem: desde rápidos scripts de instalação e administração de tarefas simples, à lógica de negócios, passando por complexos serviços distribuídos. Em todos os nossos tutoriais até agora, os idiomas usados foram funcionais. Por exemplo, usamos o idioma fold no lugar de loops while e do while, e todas as nossas variáveis foram imutáveis.

Porém, Scala é uma linguagem multi-paradigma e permite que todos os algoritmos funcionais que vimos até o momento sejam implementados também em idiomas imperativos.

Vejamos um exemplo simples:

var contador = 0;                                 
val filename = "/usr/share/dict/words"            
val origemDados = Source.fromFile(filename)       

for (linha <- origemDados.getLines() ) {
	contador = contador + 1;
}

println("Temos " + contador + " palavras no dicionário em " + filename)

O script abre o arquivo do dicionário do Linux e conta quantas linhas temos. O loop for é uma construção imperativa, e não tem valor de retorno(não confundir com a expressão for). Se o loop não possui valor de retorno, ele só pode causar efeitos colaterais, ou seja que para fazer algo útil deve, necessariamente, alterar o estado da máquina virtual. Loops imperativos podem alterar o estado da máquina imprimindo a uma via de saída, por exemplo. Ou podem simplesmente incrementar uma variável, que é o que fizemos no exemplo acima onde a variável contador é declarada com var no lugar de val, e seu valor é incrementado a cada linha que é lida do arquivo dicionário.

O seguinte trecho também lê o arquivo /usr/share/dict/words e grava em um ArrayBuffer mutável as palavras que começam com a letra A:

import scala.collection.mutable.ArrayBuffer
import scala.io.Source

var buf = new scala.collection.mutable.ArrayBuffer[String]()
                                                  
buf += "Primeiro item"                            
                                                  
val origemDados = Source.fromFile("/usr/share/dict/words")
                                                  

for ( linha <- origemDados.getLines() ) {
	if ( linha.matches("^[aA].*") ) {
		buf += linha;
	}
}

println("Palavras começando com a letra A: ")     
for ( linha <- buf ) {
	println(linha)
}

 

O ArrayBuffer é mutável e tem seu conteúdo alterado em tempo real, sem gerar uma cópia adicional e uma estrutura de dados persistente para ser descartada.

Para encerrar este post, implementamos o algoritmo Quicksort com idioma imperativo em Scala:

/*
Implementação de QuickSort de Kernighan/Pike.
Adaptado para Scala, demonstrando o idioma imperativo.
Autor: Jose Fonseca ([email protected])
*/

import scala.collection.mutable.ArrayBuffer
import scala.util.Random

trait Comparador[A] {
	def compara(a1: A, a2: A): Int
}

class ComparadorDeInteiros extends Comparador[Int] {
	def compara(a1: Int, a2: Int): scala.Int = {
		val mo: scala.Int = -1
		if (a1 < a2)  mo
		else if (a1 == a2) 0
		else 1
	}
}

class ComparadorDeStrings extends Comparador[String] {
	def compara(a1: String, a2: String): Int = {
		a1.compareTo(a2)
	}
}

class ComparadorDeStringsIgnorandoMaiusculas extends Comparador[String] {
	def compara(a1: String, a2: String): Int = {
		a1.compareToIgnoreCase(a2)
	}
}


object QuicksortImperativo {

val rnd = new Random()                            

def rand(left: Int, right: Int): Int = left + Math.abs(rnd.nextInt()) % (right - left + 1)
                                                 
def troca[A](v: ArrayBuffer[A], i: Int, j: Int): Unit = {
	var tmp: A = v(i)
	v(i) = v(j)
	v(j) = tmp
}                                                 

def quicksort[A](v: ArrayBuffer[A], left: Int, right: Int, cmp: Comparador[A]): Unit = {
	var i: Int = 0
	var ultimo: Int = 0
	if (left >= right) return;
	// move o elemento pivo para v[left]
	troca(v, left, rand(left, right));
	ultimo = left
	for {i <- left + 1 to right} {
		if (cmp.compara(v(i), v(left)) < 0) {
			ultimo = ultimo + 1
			troca(v, ultimo, i)
		}
	}
	troca(v, left, ultimo)
	quicksort(v, left, ultimo - 1, cmp)
	quicksort(v, ultimo + 1, right, cmp)
}                                                 
                                                  

def main(args: Array[String]) = {
  
  val arr = ArrayBuffer[String]("Amelia","Zorro","Bethesda","Uranio","Telefone","livro","Ronaldinho")

  QuicksortImperativo.quicksort[String](arr, 0, arr.length - 1, new ComparadorDeStrings())
  println("Nomes ordenados considerando maiusculas: " + arr.mkString(", "))

  QuicksortImperativo.quicksort[String](arr, 0, arr.length - 1, new ComparadorDeStringsIgnorandoMaiusculas())
  println("Nomes ordenados ignorando maiusculas: " + arr.mkString(", "))

  val inteiros = ArrayBuffer[Int](101,34,67,11,0,99,1001,777,663,32)

  QuicksortImperativo.quicksort[Int](inteiros, 0, inteiros.length - 1, new ComparadorDeInteiros())
  println("Inteiros ordenados : " + inteiros.mkString(", "))
  
  
 }            
}

 

O programa acima produz a seguinte saída com os dados fornecidos:
Nomes ordenados considerando maiusculas: Amelia, Bethesda, Ronaldinho, Telefone, Uranio, Zorro, livro
Nomes ordenados ignorando maiusculas: Amelia, Bethesda, livro, Ronaldinho, Telefone, Uranio, Zorro
Inteiros ordenados : 0, 11, 32, 34, 67, 99, 101, 663, 777, 1001

Conclusão
Scala permite a programação imperativa sem impor quaisquer dificuldades. O código de Quicksort apresentado em nosso exemplo é uma transcrição quase verbatim de código Java encontrado em um texto clássico de programação [KERNIGHAN. 1999.]. Todas as construções de C, Java, Perl, Python e outras linguagens tipicamente imperativas podem ser facilmente implementadas em Scala. A linguagem sugere o uso de idiomas funcionais, porém não impõe qualquer obstáculo para o trabalho com o paradigma imperativo.

Referências
KERNIGHAN, Brian e PIKE, Rob. The Practice of Programming. Editora Addison Wesley, 1999.

Standard