Fizz Buzz y Pattern Matching en Elixir

Publicado el Lunes, 4 de mayo de 2015

Fizz Buzz es un "ejercicio de entrevista de trabajo" para programadores:

Escribir un programa que muestra los números del 1 al 100. Pero para múltiplos de tres, mostrar "Fizz" en vez del número y para múltiplos de cinco mostrar "Buzz". Para números que son múltiplos de ambos mostrar "FizzBuzz".

Imran Ghory empezó a usarlo para filtrar programadores que no supieran escribir código. Aparentemente hay una cantidad alarmante de desarrolladores y personas recibidas de Ciencias de la Computación que no pueden resolver este ejercicio. Jeff Atwood escribió también sobre el tema en su blog.

Generalmente no estoy muy de acuerdo con estas pruebas de entrevista, soy más partidario de entrevistas con Pair Programming y otros enfoques. Pero de todas formas en este post no me voy a centrar en la capacidad de una persona de resolver problemas de entrevistas de trabajo.

ElixirHace poco empecé a leer el libro Programming Elixir: Functional |> Concurrent |> Pragmatic |> Fun de Dave Thomas. Creo que Elixir se está convirtiendo en mi nuevo Ruby.

Como me pasó con Ruby en su momento, estoy empezando a entrarle porque me resulta divertido y curioso. De a poco estoy leyendo las bases, escribiendo scripts cortos y aprendiendo. Va más de una noche que me quedo hasta la madrugada con mi Kindle por un lado y la laptop con el intérprete de Elixir por el otro, leyendo y probando cosas.

Uno de los conceptos nuevos del que leí fue Pattern Matching, proveniente aparentemente de Erlang (Elixir corre sobre la VM de Erlang). En Elixir, el operador = puede ser usado para asignar valor a variables. Pero se llama "match operator" y si bien sirve para asignar el valor de la derecha a una variable a la izquierda, tiene más una funcionalidad de "matchear" (¿emparejar, coincidir?) lo que hay de cada lado.

Entonces:

iex(3)> a = "Rick Sanchez"
"Rick Sanchez"
iex(4)> "Morty Smith" = a
** (MatchError) no match of right hand side value: "Rick Sanchez"
 
iex(4)> "Rick Sanchez" = a
"Rick Sanchez"

Se vuelve más interesante al momento de usar estructuras de datos:

iex(5)> {a, b, c} = {"Glip Glops", :mr_meeseeks, 128}
{"Glip Glops", :mr_meeseeks, 128}
iex(6)> a
"Glip Glops"
iex(7)> b
:mr_meeseeks
iex(8)> c
128
iex(9)> "Glip Glops" = a
"Glip Glops"
iex(10)> a
"Glip Glops"
iex(11)> {f, d} = {a, b, c}
** (MatchError) no match of right hand side value: {"Glip Glops", :mr_meeseeks, 128}

Al principio me costó entender bien cómo se podría usar, pero creo que es parte natural de adquirir un concepto de un paradigma nuevo. Un ejemplo que me ayudó bastante a ir viendo cómo usar Pattern Matching fue el ejercicio de FizzBuzz que nos hace escribir el libro.

Volviendo a FizzBuzz en sí, casualmente en el último Ruby Weekly salió un artículo FizzBuzz In Too Much Detail que analiza -como el título lo indica- el ejercicio en gran detalle. Les recomiendo leerlo, está muy bueno. Empieza analizando el código repetido de la primera implementación, pasando por los principios de DRY (no te repitas), parametrización, aplicando programación funcional y más. Vale la pena leer todos los pasos hasta llegar a la conclusión.

Un código Ruby para resolver el problema de FizzBuzz sin demasiado análisis o preocupación por el código generado puede ser:

(1..100).each do |i|
  if (i % 3 == 0 && i % 5 == 0)
    puts "FizzBuzz"
  elsif (i % 3 == 0)
    puts "Fizz"
  elsif (i % 5 == 0)
    puts "Buzz"
  else
    puts i
  end
end

Sí, hay código repetido, sí, se podría mejorar, pero es lo suficientemente bueno como para que alguien entienda a golpe de vista qué hace el código y funciona. Leyendo sobre funciones en Programming Elixir, el libro nos plantea dos ejercicios para practicar un poco el funcionamiento del lenguaje:

Escribir una función que tome 3 argumentos, si los primeros dos son 0, devolver "FizzBuzz". Si el primero es 0, devolver "Fizz", y si el segundo es 0, devolver "Buzz". Si no se cumplen éstas condiciones, devolver el tercer argumento.

El objetivo era agarrar el concepto de que una función (anónima en este caso) en Elixir puede tener varios cuerpos.

El código que escribí fue el siguiente:

fizzbuzz = fn
  (0, 0, _) -> "FizzBuzz"
  (0, _, _) -> "Fizz"
  (_, 0, _) -> "Buzz"
  (_, _, a) -> a
end

La variable fizzbuzz está guardando la función anónima que defino a continuación. La variable "_" (guión bajo o underscore) matchea lo que venga. Por eso lo único que me importa en el código en la parte donde defino los parámetros que quiero recibir -(parámetros) -> cuerpo de la función- son los ceros, en los demás casos pongo guiones y en el caso de que no sean ceros, nombro el tercer parámetro para devolverlo.

Estoy haciendo pattern matching de los parámetros que recibe la función con los distintos "cuerpos". Acá fue donde mi cabeza hizo un clic y dije "para ésto sirve el pattern matching".

I see your value now

IO.puts "FizzBuzz (0,0,3) = #{fizzbuzz.(0,0,3)}"
IO.puts "FizzBuzz (0,2,3) = #{fizzbuzz.(0,2,3)}"
IO.puts "FizzBuzz (1,0,3) = #{fizzbuzz.(1,0,3)}"
IO.puts "FizzBuzz (1,2,3) = #{fizzbuzz.(1,2,3)}"

Esto imprime como pide el ejercicio lo siguiente:

FizzBuzz (0,0,3) = FizzBuzz
FizzBuzz (0,2,3) = Fizz
FizzBuzz (1,0,3) = Buzz
FizzBuzz (1,2,3) = 3

Ahora la segunda parte nos pide que usemos el operador rem(a, b) que devuelve el resto de la división de los parámetros. Debemos escribir una función que reciba un entero (n) y llame a la función anterior (la que llamé fizzbuzz en mi caso) pasándole rem(n, 3), rem(n, 5) y n. Después llamarla siete veces con los argumentos 10, 11, 12, etc. El resultado es otra solución a FizzBuzz pero sin usar lógica condicional:

nuevo_fizzbuzz = fn(n) ->
  fizzbuzz.(rem(n, 3), rem(n,5), n)
end

Y el resultado:

iex(9)> nuevo_fizzbuzz.(11)
11
iex(10)> nuevo_fizzbuzz.(12)
"Fizz"
iex(11)> nuevo_fizzbuzz.(13)
13
iex(12)> nuevo_fizzbuzz.(14)
14
iex(13)> nuevo_fizzbuzz.(15)
"FizzBuzz"
iex(14)> nuevo_fizzbuzz.(16)
16
iex(15)> nuevo_fizzbuzz.(17)
17
iex(16)> nuevo_fizzbuzz.(18)
"Fizz"

Programming ElixirEsto es apenas una muestra mínima de cómo un ejercicio simple nos puede dar a entender un concepto. En mi caso sirvió, aunque el libro mismo me da a entender que tengo mucho más por aprender al respecto de Pattern Matching y demás conceptos de programación funcional. Compartir por acá me ayuda a aferrar un poco más los conceptos y en una de esas animar a alguien a probar Elixir, o aprender algo nuevo de alguien que esté leyendo esto.

Estoy bastante entretenido con el libro, lo recomiendo como introducción al lenguaje. Recién voy por lo básico: pattern matching, inmutabilidad, las bases de Elixir y funciones anónimas. Pero me viene divirtiendo y los ejercicios prácticos por más que parezcan simples ayudan a aferrar los conceptos.

No sé si Elixir será mi próximo lenguaje de cabecera, pero pienso seguir leyendo y ver si eventualmente empiezo a programar cosas con esta tecnología. En el peor de los casos, me está ayudando a pensar las cosas de otra manera y sólo eso ya es un gran aporte. Si les interesa conseguirlo, visiten el sitio The Pragmatic Bookshelf (disponible en papel y/o digital)

2 comentarios en este post

Feed de comentarios

Dejar un comentario

Toasty!