Por qué Ruby mola


Este artículo me resulta muy complejo. Así como en el resto de la saga de los "por qué UN_LENGUAJE mola" he tratado de ser todo lo imparcial que he podido, temo que en este caso me sea imposible.

Y la causa es muy sencilla: mi propia ignorancia.

Nunca he hecho nada en Ruby, salvo algún kata que otro. Pero me estoy leyendo el libro "7 languages in 7 weeks" (libro muy recomendable) y he aprovechado para tomar algunas notas.

Así que éste es el artículo: "Por qué ruby mola, en mi opinión".

Admitiré todo tipo de críticas y sugerencias

Por qué Ruby me mola

Toda expresión devuelve algo

Creo que es una cosa que me gusta mucho. No tienes que preguntarte si un método devuelve algo, ya que todos lo hacen. Aunque no quieran.

Como azúcar sintáctico es algo genial. Pero mejor no hablaremos de los posibles problemas de cambio de API simplemente por añadir una instrucción más, sobreescritura de un return implícito o del coste en rendimiento de apilar/desapilar muchos valores que nunca se utilizarán.

Aún así, me gusta.

Condiciones inline

Tener la posibilidad de escribir una condición en una línea facilita la lectura de código. Eso es algo que me gusta del lenguaje.

Por si alguien tiene dudas, me refiero a esto:

.. code:: ruby

raise "erroneous filename" if ! file.exists?

Nombres de funciones con los decoradores ? y !

Venga, va: Me encanta eso de preguntar cosas y que el lenguaje quede legible. Sin embargo creo que debería ser más restrictivo, y prohibir que este tipo de funciones admita parámetros. ¿Por qué? Fijaos:

.. code:: ruby

if file.exists? do
  file.remove!
end

Queda claro dónde estamos preguntando y que vamos a realizar una operación que modifica el objeto actual.

Sin embargo, si añadimos parámetros a estas funciones la cosa cambia mucho:

.. code:: ruby

if fs.exists? filename do
  fs.remove! filename
end

Creo que queda claro a lo que me refiero :D

Duck typing

Si algo parece un pato y hace "cuac" como un pato, es un pato. Ni qué decir tiene que, si me gusta en Python, me tiene que gustar también en Ruby.

Con esto se hace referencia a que es innecesario indicar qué interfaces se implementan en una clase, ya que basta con que implemente la funcionalidad adecuada.

Tipado dinámico

Nuevamente una característica que también tiene Python y que también me gusta.

Symbols

La posibilidad de definir constantes invaluadas me gusta. No son pocas las veces que necesito una constante sólo a modo de documentación, y a veces necesito que sean valores únicos.

Ya que estoy comparando con Python... ¡Python no lo tiene!

Bloques de código

Apesar de que tienen sus problemas potenciales, me gustan los bloques de código. Evitan la creación de funciones innecesarias porque se va a realizar una tarea simple. Me viene a la cabeza el ejemplo de ordenar un vector de objetos. En Java necesitaría una clase que implemente una interfaz Comparable, de manera que pueda compararme dos objetos... En ruby puedo hacer lo mismo en una línea.

Quizá tienen demasiada potencia y pueden dar lugar a código duplicado. Aquí entra en juego la experiencia y habilidad del programador.

En Python existe algo parecido aunque más limitado, que es el lambda.

Clases abiertas

Aunque vuelve a ser una espada de Damocles, me gustan las clases abiertas. No son pocas las veces que he deseado añadir una pequeña funcionalidad a una clase existente con el fin de ajustarla a mis necesidades, en lugar de tener que crear todo un wrapper por encima.

Por qué Ruby no me mola

Cláusula Unless

Cada vez que me encuentro esta cláusula tengo que releer la expresión 3 veces para estar seguro de lo que hace. Y eso que es un simple "if not". Además, puede dar lugar a código difícil de leer, si se mezcla con condiciones habituales:

.. code:: ruby

unless file.exists? do
  if another_file.exists? and not file == another_file do
    # some stuff
  end
end

Cero es True

A mí me resulta poco intuitivo. Será porque he sido programador de Ansi C durante mucho tiempo. Me resulta más sencillo pensar que algo que devuelve cero, es Falso.

Paréntesis opcionales al llamar a una función

Aunque en muchas ocasiones permite una mayor legibilidad del código, yo prefiero que las cosas se hagan siempre de la misma manera. Si la manera de invocar a una función es mediante paréntesis, pues se ponen.

Los tres puntos

Me parece demasiado afinar la diferencia entre .. y ..., ya que resulta fácil confundirlos visualmente.

Parámetros nombrados

A mí me parecen muy interesantes los parámetros nombrados, ya que pueden ofrecer mucha semántica al código, sobre todo cuando algo deja de ser lo que es para ser otra cosa (python):

.. code:: python

create_output(output_filename=current_path)

Ruby carece de esta característica, aunque podéis encontrar ejemplos en los que se pasa una hash como parámetro a la función. Horrible.

Hay dos maneras de crear bloques

La sintaxis debería ser más simple, y la manera de hacerlo es reducir las formas de hacer las cosas. En Ruby podemos utilizar el do/end o bien el {/} para hacer lo mismo. Si una manera es buena, ¿por qué ofrecer una alternativa?

No sé si me mola en Ruby

No me gustan las evaluaciones automáticas de Strings. No sé si será porque me recuerda a PHP, pero el caso es que me parece muy arriesgado que por cambiar el tipo de comillas se pierda rendimiento.

Sinceramente, me gusta cómo hace esto mismo Python, utilizando Templates. Si necesito una plantilla, ya usaré plantillas.

Conclusiones

Cuando comencé leerme el libro, Ruby me parecía un lenguaje muy curioso. Tiene muchas cosas muy molonas.

Tras leerme el capítulo de Ruby, mi opinión ha cambiado drásticamente. No me gusta el lenguaje. Me parece innecesariamente complejo y, contra lo que defienden sus seguidores, me parece ofuscado.

Comparativa Ruby-Python

Con el fin de explicar por qué me parece ofuscado, aquí tenéis una comparativa entre un código en Ruby y uno en Python. Se trata de un "grep" pequeñito. Por favor, si algún programador de algún lenguaje quiere mejorarlo, se aceptan sugerencias, siempre y cuando haga exactamente lo mismo.

Lo que hará es buscar una expresión regular en el directorio actual (no es recursivo), mostrando el nombre del archivo, dos puntos, línea donde se encontró.

Ruby:

.. code:: ruby

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#!/usr/bin/ruby

expression = Regexp.new(ARGV[0])

Dir.foreach('.') do |filename|
  file = File.new(filename)
  if File.file? filename:
    File.foreach(filename) do |line|
      if line =~ expression:
        puts "#{filename}:#{line}"
      end
    end
  end
end

Python: .. code:: python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/usr/bin/python

import re
import os
import sys

expression = sys.argv[1]

for filename in os.listdir('.'):
  if os.path.isfile(filename):
    with open(filename) as content:
      for line in content:
        line = line.rstrip()
        if re.match(expression, line):
          print "{filename}:{line}".format(filename=filename, line=line)

Solución Ruby aportada por Juan F. Pérez, con una pequeña modificación: .. code:: python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/usr/bin/ruby

expression = Regexp.new(ARGV[0])

Dir.foreach('.') do |filename|
  next unless File.file? filename
  File.open(filename).each do |line|
    puts "#{filename}:#{line}" if line =~ expression
  end
end

Sed sinceros y pensad cuál resulta más legible.


Comentarios

Comments powered by Disqus