Meu primeiro post bem técnico sobre Scala, vamos ver no que dá (rs).
Bom, primeiro, um pouco de “background”: comecei o fantástico curso de Machine Learning no site coursera.org, e todas as lições que estão no site são em Octave/Matlab. Não conhecia Octave, muito menos Matlab, e quando programei nestas linguagens descobri algumas coisas meio estranhas (bom, eu pelo menos considero estranho que, ao esquecer de colocar um “;” no final de um comando, ele imprima o resultado na tela), além, claro, de não ser possível programar pra Android, por exemplo.
Como estou estudando Scala, e como já consegui fazer Scala rodar no Android de um jeito menos “doloroso” (mais sobre isso em outro post), resolvi fazer os códigos em Scala. Há bibliotecas boas de multiplicação de matrizes em Scala, porém comparadas com as versões Java, elas são BEM mais lentas. Foi aí que pensei: por que não encapsular uma dessas bibliotecas Java em Scala?
Tudo correu bem, até o momento que resolvi que queria que cada matriz funcionasse como um Traversable. Para quem é de Ruby, o Traversable é equivalente ao Enumerable, com algumas coisas a menos e outras a mais. Só para comparar, vamos ver o código dos dois (para simplificar, vou omitir alguns códigos, mas a API é a seguinte: Matrix#rows e Matrix#cols para saber o número de linhas e colunas, Matrix#* multiplica matrizes ou uma matriz com um número. Também, a matriz possui um atributo, “list”, que contém um array de arrays com os elementos internos da matriz).
class Matrix(list: List[List[Double]]) extends Traversable[Double] {
def cols = 3 //Só pro nossos códigos de exemplo compilarem...
def foreach[B](function: Double => B) = list.flatten.foreach { e => function(e) }
}
class Matrix
include Enumerable
def each(&b)
list.flatten.each &b
end
end
Repare que o código é semelhante nas duas linguagens, a diferença básica é que em Ruby não precisamos definir tipos, em Scala precisamos (não que nesse caso faça alguma diferença). Só que temos um caso engraçado tanto em Ruby como em Scala: se queremos um método que, digamos, eleve todos os elementos ao quadrado, elemento por elemento, ao usar “map” para fazer esse processo, ele vai nos retornar, em Ruby, um Array, e em Scala, um Traversable[Double]. E pior, os elementos vão ficar “achatados”. Em ambos, é possível resolver isso fazendo o “each” (ou o “foreach”) retornar uma lista de elementos que está em cada linha, mas ainda assim temos o problema que nosso retorno será semelhante a um “array de arrays”, não uma matriz. O que fazer?
Bom, em Ruby, a gente se ferrou, literalmente. Não temos uma maneira de fazer isso que não envolva sobrescrever o map, e todos os métodos que constrõem uma lista a partir de outra. Em Scala, temos “implicits”, e um conceito “canBuildFrom”.
(more…)