Traducción Scala School

Traducimos el tutorial Scala School de Twitter.

Scala School un tutorial rápido a Scala
Scala School un tutorial rápido a Scala

Este post es para informarles que iniciamos la traducción del tutorial de Scala School de Twitter. El tutorial de Scala School incluye una rápida introducción al lenguaje en 13 secciones:

  • Basicos
  • Continuación de básicos
  • Collecciones
  • Coincidencia de patrones & composición funcional
  • Tipos y los básicos de polimorfismo
  • Tipos avanzados
  • Simple Build Tool
  • Más sobre collecciones
  • Testing con specs
  • Concurrencia en Scala
  • Java + Scala
  • Introducción a Finagle
  • Searchbird

Si quisieran cooperar en la traducción del tutorial hay issues abiertos en nuestro fork de Scala School en Github. Pueden tomar cualquiera que no tenga una persona asignada, los invitamos a participar.

break

Funciones en Scala

Funciones en Scala

En este post haremos un recorrido sobre el uso y definición de funciones en Scala. Mostraremos con ejemplos:

  • cómo definir funciones
  • cómo crear funciones anónimas
  • qué son y cómo se usan los closures
  • qué son y cómo se usan las funciones de orden superior

Definiendo funciones en Scala

En Scala las definiciones de funciones empiezan con la palabra reservada def seguidas del identificador de la función, una lista de parámetros separados por comas y encerrados entre paréntesis.

Scala es un lenguage de tipado estático, así que debemos indicar el tipo de cada parámetro en una función después de su nombre y separado por :. Esto es debido a que Scala no infiere los tipos de los parámetros en las funciones.

Un ejemplo simple es una función que eleva al cuadrado un número entero.

1
2
3
def square(x: Int): Int = {
  x * x
}

Después del nombre y los parámetros debemos indicar el tipo de resultado que devuelve una función con una anotación de tipo ( : Int en nuestro ejemplo). El cuerpo de la función va después del tipo de resultado, representado por un signo = seguido de corchetes.

El signo igual que precede al cuerpo hace referencia a la parte funcional del lenguage que indica que una función define una expresión que resulta en un valor.

Un detalle que podemos señalar es que las funciones en Scala no requieren forzosamente de un return como podemos observar en nuestro ejemplo. El resultado de la última expresión que se evalúa en una función es interpretado de forma automática como el valor devuelto por dicha función.

Un ejemplo donde debemos usar la palabra return es en el caso de las condicionales

1
2
3
4
5
6
def min(a: Int, b: Int): Int = {
  if (a < b) {
    return a
  }
  b
}

En este ejemplo donde definimos una función min, si el valor de a es menor que b es necesario usar un return en la línea 3, ya que de otro modo, la función siempre devolvería el valor de b.

Otro detalle de las funciones de Scala, es que no es obligatorio especificar el tipo de resultado de una función, ya que el compilador puede inferirlo. Así nuestro ejemplo de función square puede escribirse como:

1
2
3
def square(x: Int) = {
  x * x
}

Hay dos excepciones para esta regla, una es cuando usamos return en el cuerpo de la función. Nuestra función square no puede entonces omitir el tipo devuelto.

La otra excepción ocurre para funciones recursivas. Un ejemplo de una función recursiva es la implementación del algoritmo de Euclides para encontrar el máximo común divisor de dos números. Por ejemplo:

1
2
3
4
5
6
7
def gcd(a: Int, b: Int): Int = {
  if (b == 0) {
    a
  } else {
    gcd(b, a % b)
  }
}

La función gcd tampoco puede omitir el tipo de resultado.

Hay ocasiones en que necesitamos declarar funciones que no devuelven ningún valor interesante, por ejemplo:

1
2
3
def greet() = {
  println("Hello, world!")
}

El tipo devuelto inferido por Scala en este ejemplo es Unit. El tipo Unit en Scala es similar al void de Java.

Por último, si el cuerpo de una función puede escribirse en una solo línea, las llaves del cuerpo pueden omitirse, así nuestras funciones min, square, gcd y greet pueden escribirse como:

def square(x: Int) =  x * x
def min(a: Int, b: Int) =  if (a < b) a else b
def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b)
def greet() = println("Hello, world!")

Funciones anónimas

Una función anónima, también llamada función literal o función Lambda, es una definición de una función que no está ligada a un identificador.

Scala tiene una sintaxis simple para definir funciones anónimas. La siguiente expresión crea una función sucesor anónima para números enteros:

1
(x: Int) => x + 1

El operador => indica que la función convierte el argumento que pasemos del lado izquierdo (cualquier número entero x) al resultado de evaluar la expresión que está del lado derecho ( x + 1 ). Por lo tanto, esta función mapea cualquier entero x con x + 1 (el sucesor de x).

Una función literal se compila como una clase anónima que cuando se instancia en tiempo de ejecución se convierte en un valor función. De este modo, una función literal existe en el código fuente, mientras que un valor función es un objeto en tiempo de ejecución. La definición de clase para nuestra función anónima que genera el compilador de Scala es similar a la siguiente:

1
2
3
new Function1[Int, Int] {
  def apply(x: Int): Int = x + 1
}

Los valores función son objetos, así que podemos guardarlos en variables, pero como también son funciones podemos invocarlos usando paréntesis.

1
2
val successor = (x: Int) => x + 1
successor(10)

Una función anónima puede recibir cualquier numero de argumentos, por ejemplo:

1
(x: Int, y: Int) => x + y

Closures

Nuestro ejemplo de función Lambda, la función sucesor, es una función completa, ya que el valor de x es proporcionado como argumento a la función.

Pensemos ahora en la función:

1
(x: Int) => x + y

En este caso, desde el punto de vista de la función, y es una variable libre, porque la función literal, por sí sola, no le da un signifcado a y. La variable x por el contrario, es una variable ligada, porque tiene significado en el contexto de la función, está definida como su parámetro. Por lo tanto, no será posible compilar esta función, si y no está dentro de su alcance.

1
2
3
val y = 3
val adder = (x: Int) => x + y
adder(4) // returns 7

Este ejemplo es posible, ya que al momento que se asigna el valor función adder, el valor se completa, de ahí el término en inglés closure (conclusión o cierre). El compilador revisa todas las variables en el alcance y asigna los valores necesarios al valor función (objeto) adder, es decir, lo completa.

El valor función adder es a lo que se conoce como closure (conclusión) no a la declaración de la función en sí. Una vez que el objeto (valor función) se concluye o completa, podemos moverlo a otra parte de la aplicación donde y esté completamente fuera del alcance y aún así se referiría al valor original que tenía en el alcance donde se completó el valor función.

Funciones de orden superior

Uno de los usos más comunes de los closures es pasarlos como argumentos para funciones de orden superior. Una función de orden superior o functor, es una función que hace al menos una de las siguientes cosas:

  • Toma una o más funciones como entrada
  • Devuelve una función

Por ejemplo, supongamos que tenemos una implementación del juego de FizzBuzz en la siguiente función:

1
2
3
4
5
6
7
def fizz_buzz() = {
  for (i <- 1 to 100) {
    if (i % 3 == 0) println("Fizz ")
    else if (i % 5 == 0) println("Buzz ")
    else println(i +  " ")
  }
}

Supongamos que queremos reescribirla para que funcione para cualquier variación del juego, por ejemplo Fizz Buzz Woof, donde Woof corresponde a múltiplos de 7 y que además podamos combinar palabras para cuando un número sea múltiplo de 3 y 5 o de 3 y 7 (FizzBuzz y FizzWoof respectivamente). Si usamos closures podemos generalizar nuestro problema para cualquier múltiplo que queramos agregar.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def fizz_buzz() = {
  val lambdas = Array(
    (x: Int) => if (x % 3 == 0) "Fizz" else "",
    (x: Int) => if (x % 5 == 0) "Buzz" else "",
    (x: Int) => if (x % 7 == 0) "Woof" else ""
  )
  for (i <- 1 to 100) {
    println(next_value(lambdas, i))
  }
}
def next_value(lambdas: Array[(Int) => String], x: Int) = {
  var value = ""
  for (lambda <- lambdas) {
    value = value + lambda.apply(x)
  }
  if (value == "")  x.toString() else value
}

En este ejemplo la función next_value es una función de orden superior que recibe como argumentos un arreglo de funciones del tipo (Int) => String que indica que cada elemento en el arreglo debe ser una función que reciba como argumento un entero y que devuelva como resultado una cadena (línea 11).

Dentro del método next_value lo que hacemos es aplicar cada una de las funciones del arreglo lambdas a nuestro argumento x (x es uno de los valores del 1 al 100) usando el método apply, esto es posible ya que como habíamos explicado antes los valores función son en realidad objetos.

Así, dentro de la función fizz_buzz lo único que tenemos que hacer es generar el arreglo con las funciones Lambda que reemplazan los múltiplos de un número por palabras. Así, si quisieramos reemplazar los múltiplos de 11 por la palabra Bazz lo único que tenemos que hacer es agregar una función más al arreglo lambdas.

Espero que este post te haya servido, si tienes alguna duda, queja, sugerencia o reclamación, no dudes en buscarme en Twitter

Un análisis básico sobre la ventaja de implementar algoritmos en el lenguaje Scala

###¿Por qué Scala?###

Scala es un lenguaje híbrido y con un gran poder para el desarrollo de software robusto, basta con ver los ejemplos de Coursera y Twitter para saber porque. Es por ello que en este post, pretendo mostrar algunas características a la hora de implementar un algoritmo de ordenamiento en Scala.

Empezaré con una características propia de los lenguajes funcionales (como Haskell,OCaml,F# entre otros) y es la inferencia de tipos.

La inferencia de tipos asigna automáticamente un tipo de datos a una función sin necesidad de que el programador lo escriba.

El tipo de las funciones es reconstruido a partir de un análisis estático del programa realizado por el compilador o intérprete del lenguaje,

A partir de las definiciones previas y del uso de las variables en el cuerpo de las funciones.

A continuación se muestra 3 ejemplos del algoritmo QuickSort en Java y Scala, notar la diferencia entre las primeras dos implementaciones y compararla con la última implementación y ver la diferencias.

QuickSort implementado en Java.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package de.vogella.algorithms.sort.quicksort;

public class Quicksort  {
  private int[] numbers;
  private int number;

  public void sort(int[] values) {
    if (values ==null || values.length==0){
      return;
    }
    this.numbers = values;
    number = values.length;
    quicksort(0, number - 1);
  }

  private void quicksort(int low, int high) {
    int i = low, j = high;
    int pivot = numbers[low + (high-low)/2];
    while (i <= j) {
      while (numbers[i] < pivot) {
        i++;
      }
      while (numbers[j] > pivot) {
        j--;
      }
      if (i <= j) {
        exchange(i, j);
        i++;
        j--;
      }
    }
    if (low < j)
      quicksort(low, j);
    if (i < high)
      quicksort(i, high);
  }

  private void exchange(int i, int j) {
    int temp = numbers[i];
    numbers[i] = numbers[j];
    numbers[j] = temp;
  }
}

Este es la implementacion en Scala.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def sort(xs: Array[Int]) {
  def swap(i: Int, j: Int) {
    val t = xs(i); xs(i) = xs(j); xs(j) = t
  }
  def sort1(l: Int, r: Int) {
    val pivot = xs((l + r) / 2)
      var i = l; var j = r
      while (i <= j) {
        while (xs(i) < pivot) i += 1
          while (xs(j) > pivot) j -= 1
            if (i <= j) {
              swap(i, j)
                i += 1
                j -= 1
            }
      }
    if (l < j) sort1(l, j)
      if (j < r) sort1(i, r)
  }
  sort1(0, xs.length - 1)
}

La siguiente implementación fue escrita en Scala, pero con los principios de la programación funcional y aprovechando la inferencia de tipos:

1
2
3
4
5
6
7
8
9
10
def sort(xs: Array[Int]): Array[Int] = {
  if (xs.length <= 1) xs
  else {
    val pivot = xs(xs.length / 2)
      Array.concat(
          sort(xs filter (pivot >)),
          xs filter (pivot ==),
          sort(xs filter (pivot <)))
  }
}

El algoritmo implementado de manera funcional captura la esencia del algoritmo QuickSort de manera consisa:

  • Si el arreglo está vacío o consta de un solo elemento,entonces ya está ordenado, de modo que se devuelve inmediatamente.

  • Si el arreglo no esta vacío, elije un elemento y usalo como pivote.

  • Se reparte el arreglo en dos sub-arreglos que contienen elementos que son más pequeños que, respectivamente mayor que el elemento de pivote, y una tercera matriz que contiene elementos iguales al elemento pivote.

  • Clasificar los primeros dos sub-arreglos por una invocación recursiva de la función sort

  • El resultado se obtiene añadiendo los tres sub-arreglos.

###Nota### Si no tienes idea de como funciona el algoritmo Quicksort, recomiendo que veas esta Animación de QuickSort.

###Referencia###

Participa en el blog de ScalaMX.

##Lineamientos para la participación

Este blog es abierto e inclusivo por definición, queremos que sea un espacio donde personas dentro de nuestra comunidad ofrezcan sus opiniones y consejos a través de este blog. A los que están fuera, también les queremos dar la oportunidad de escribir contenidos que puedan enriquecer nuestro entendimiento en el espectro de arquitecturas de software, soluciones de cómputo reactivo, así como de cómputo distribuido.

Nuestro blog está alojado en Github, por lo que te invitamos a clonar el repositorio y a mandarnos un pull request:

# Una pequeña reseña de lo que necesitas hacer.
$ git clone git@github.com:scalamx/scalamx.github.io.git scalamx
$ cd scalamx
$ gem install bundler
$ bundle install
$ jekyll serve --watch
# Visita el servidor local en: http://localhost:4000
# Edita tu post en markdown (MD) en la carpeta _posts/

Queremos fomentar en esta comunidad el desarrollo de buenas prácticas, el valor de compartir conocimiento y la colaboración.

¡Esperamos tu colaboración!

Bienvenidos a la comunidad Scala México

Te damos la bienvenida a la Comunidad Scala México. Tenemos como misión aportar recursos para que los desarrolladores de habla hispana -latinoamericanos y mexicanos- formen capacidades en el lenguaje de programación Scala.

Nuestro enfoque es híbrido. Esto significa que buscamos proponer arquitecturas y soluciones con bloques de construcción variados: tecnologías y lenguajes donde los programadores cuenten con experiencia y al mismo tiempo, capacidad para integrar y conectar elegantemente lo mejor de cada tecnología usando como ventaja las características que el lenguaje de programación Scala posee.

La finalidad de este esfuerzo consiste en proporcionar un medio factible y simple para formar una comunidad de expertos en Software Distribuido, pero al mismo tiempo abrir oportunidades de mercado y de negocios para aquellos emprendedores de software que enfrentan el problema de la integración de soluciones robustas.

Scala cuenta con una base de usuarios y desarrolladores con un alto potencial en América Latina; creemos que deben formarse los recursos comunitarios y las herramientas que ayudarán a los desarrolladores de habla hispana a mejorar sus prácticas, conocer nuevas tecnologías y competir en un mercado de alto valor con patrones y prácticas de excelencia y calidad. Por ello invitamos a los programadores y entusiastas del software a participar con sus contribuciones, puntos de vista y aportaciones constructivas en un entorno colaborativo y cooperativista.

Te invitamos a formar parte de las publicaciones regulares del blog de la comunidad, a proponer proyectos y a discutir tus ideas con nosotros. Las contribuciones de la comunidad te ayudarán a conocer recursos, documentación, reseñas y consejos sobre las experiencias de sus integrantes.

Bienvenido a este esfuerzo comunitario y no olvides que el Software bien hecho es aquel que se realiza efectivamente “en tiempo y presupuesto”. Scala constituye una excelente opción para realizar un cambio de paradigma en la hibridación de software bien hecho. ¡Aprovechemos la oportunidad!