En la primera parte de esta serie vimos una primera introducción a Scala como lenguaje de programación definiendo algunas características de su sintaxis. En esta segunda parte pasaremos a la práctica utilizando una Code Kata.
En las artes marciales, se denomina kata a una representación, individual o colectiva, de un conjunto de movimientos. En disciplinas como el Karate se suelen enseñar en exhibiciones. El objetivo de las mismas es repetir los movimientos una y otra vez hasta que los mismos surjan de manera natural.
En programación, denominamos code kata a una definición de un problema relativamente simple, que resolvemos una y otra vez cuando estamos aprendiendo un lenguaje o una técnica como TDD. En este caso elegiremos una kata llamada Karate Chop, que define lo siguiente:
Dada una lista ordenada y un número, devolver la posición del mismo en la lista o -1 si no se encuentra, utilizando búsqueda binaria para ello.
El objetivo en este caso era ver si podía aprender lo mínimo necesario para poder escribir una solución a esta Kata utilizando Scala y utilizando la sintaxis de programación estructurada o orientada a objetos a la que estoy acostumbrado. Tras varias iteraciones agregando tests y condiciones, llegué al siguiente resultado:
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 chop (target: Int, searchBox: Array[Int]) : Int = { | |
val size = searchBox.size | |
if (size == 0) { | |
return –1 | |
} | |
if (size == 1){ | |
if(searchBox(0) == target){ | |
return 0 | |
} else { | |
return –1 | |
} | |
} | |
val pointer = searchBox.size / 2 | |
val searchResult = searchBox(pointer) | |
if(searchResult == target){ | |
return pointer | |
} | |
if (searchResult < target){ | |
val partialResult = chop(target, searchBox.slice(pointer, size)) | |
if(partialResult == –1) | |
return –1 | |
return pointer + partialResult | |
} | |
if (searchResult > target){ | |
return chop(target, searchBox.slice(0, pointer)); | |
} | |
return –1 | |
} |
Para hacer las pruebas se suele usar un framework como ScalaTest, o recurrir a JUnit o TestNG de Java, que, como hemos mencionado anteriormente, son compatibles. En mi caso me limité a crear una pequeña función auxiliar que simplemente comparaba el resultado de la función y escribía por pantalla si había discrepancias:
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 assert(expected: Int, actual: Int){ | |
if(expected == actual){ | |
println("OK"); | |
} | |
else { | |
println("Error, expected: " + expected + " actual: "+ actual); | |
} | |
} |
Finalmente, para comprobar que el código «funciona» he usado el siguiente conjunto de casos de prueba:
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 main(args: Array[String]){ | |
test(–1, 2, Array.emptyIntArray) | |
test(0, 1, Array(1,2,3,4,5)) | |
test(2, 3, Array(1,2,3,4,5)) | |
test(2, 3, Array(1,2,3)) | |
test(0, 1, Array(1)) | |
test(4, 5, Array(1,2,3,4,5)) | |
test(3, 4, Array(1,2,3,4,5)) | |
test(1, 2, Array(1,2,3,4,5)) | |
test(–1, 0, Array(1,2,3,4,5)) | |
test(–1, 7, Array(1,2,3,4,5)) | |
} | |
def test(expected: Int, target: Int, searchBox: Array[Int]): Unit ={ | |
assert(expected, chop(target, searchBox)) | |
} |
Escribiendo esta función aprendí varias cosas sobre Scala:
- No necesitamos escribir el tipo de dato, ya que tenemos las palabras reservadas var para variables y val para objetos inmutables. Scala infiere el tipo de dato en tiempo de compilación por nosotros de la misma manera que lo hace C#, de manera que tenemos un sistema de tipos sólido y más sencillo de escribir.
-
Como curiosidades de sintaxis, el tipo de respuesta de una función lo definimos al final de la misma, en la definición de los parámetros también se declara primero el nombre y después el tipo, y el acceso a arrays se realiza mediante paréntesis en vez de corchetes.
#Conclusiones
Lo más importante que podemos ver en este ejemplo, es que no necesitamos cambiar nuestra manera de programar para comenzar a usar Scala, y que podemos ir agregando características funcionales de lenguaje a nuestro código poco a poco.
En el siguiente artículo de la serie volveremos a la teoría, retomando por donde lo habíamos dejado, viendo características como las Case Classes, el reconocimiento de patrones, y otros ejemplos de código, más funcionales que podemos encontrar en GitHub.
Mientras tanto, aquí están algunos enlaces sobre coding katas que he usado para hacer este artículo:
- El código completo de la kata está disponible en: https://github.com/rlbisbe/codekatas/blob/master/src/main/scala/KarateChop.scala
- Un artículo apoyando las code katas: http://www.peterprovost.org/blog/2012/05/02/kata-the-only-way-to-learn-tdd/
- Un artículo cuestionando las code katas: http://codekata.com/kata/kata02-karate-chop/
- La kata original que he usado en este artículo: https://hackhands.com/dont-code-katas/