Thursday, September 6, 2007

El lenguaje de programación Ruby

Por Javier Smaldone
http://www.smaldone.com.ar

Miembro de GxRioCuarto


Historia

El origen de este lenguaje es bastante atípico. Fue desarrollado por el japonés Yukihiro Matsumoto (más conocido como "matz") en el año 1995 y durante sus primeros años de vida su uso no se extendió más alla de la frontera de Japón, debido a la falta de documentación en otro idioma. A finales del año 2001, gracias a la aparición de un libro de O'Reilly en inglés [0] , comenzó a ser conocido internacionalmente y en el 2005 alcanzó la fama a través de "Ruby on Rails", un framework para el desarrollo de aplicaciones web escrito en Ruby.
Un detalle curioso aparece si comparamos la difusión de Ruby con la de Python. En los últimos 10 años, Python (que data de fines de los '80) ha sido el lenguaje de scripting de mayor crecimiento en todo el mundo. El auge de Ruby está apenas empezando y en los últimos 2 años ha tenido un crecimiento mucho mayor a Python. Sin embargo en Japón, donde ambos lenguajes han sido conocidos desde su comienzo, Ruby siempre ha sido más usado que Python.
Muchos programadores se acercan a Ruby a través de "Ruby on Rails" (las aplicaciones desarrolladas con este framework son, básicamente, programas Ruby que usan las librerías de RoR). Sin embargo no debemos confundirnos: el hecho de que Rails esté desarrollado y permita desarrollar en Ruby, no significa que este lenguaje sirva exclusivamente para construir aplicaciones web. En realidad, son las virtudes de este último las que han posibilitado desarrollar un framework tan potente y simple como Rails.

Características

Ruby es un lenguaje totalmente orientado a objetos que incorpora características de SmallTalk, Lisp, Eiffel, Ada y Perl. En palabras de su creador: "Quería un lenguaje de scripting que fuera más potente que Perl y más orientado a objetos que Python[1] ".
En rigor, el lenguaje no incorpora ninguna característica realmente innovadora (inclusive, "revitaliza" algunas técnicas presentes en Lisp en 1958 y en SmallTalk en 1980). Su principal virtud es la forma simple en que integran mecanismos como orientación a objetos, clausuras y macros, entre otros. El resultado es un lenguaje simple (de fácil lectura), pequeño (muy pocas palabras reservadas y construcciones) y extremadamente potente (con una expresividad muy cercana a la de los lenguajes funcionales).
Ruby, como todo lenguaje de scripting, es interpretado. Existen varias implementaciones del intérprete, además de la "oficial". Una facilidad adicional, tanto para el aprendizaje como para la depuración de programas, es la disponibilidad de "irb", un intérprete interactivo (ejecutado en la línea de comandos), que permite tanto "jugar" e investigar el lenguaje, como también depurar clases de un sistema complejo.


Presentación del lenguaje

En Ruby todos son objetos. La forma de invocar un método en un objeto, es mediante expresiones "objeto.metodo". El uso de paréntesis en este tipo de invocaciones es totalmente opcional, de manera que "cuenta.depositar(20)" puede escribirse también como "cuenta.depositar 20". Otra característica llamativa es que los nombres de métodos pueden incluir, por ejemplo, el caracter "=", de manera que podemos definir un método "saldo=(valor)" y luego escribir "cuenta.saldo = valor" (nótese el espacio introducido antes de "=", permitido gracias al "azúcar sintáctica" de Ruby).

Veamos algunos ejemplos:

class Cuenta
 
  def initialize(saldo_inicial)
    @saldo = saldo_inicial
  end
 
  def saldo
    @saldo
  end
 
  def depositar(monto)
    @saldo += monto
  end
 
  def extraer(monto)
     if monto < @saldo then
       @saldo -= monto
       return true
     else
       return false
     end
  end
end
 
a = Cuenta.new 20
puts a.saldo          
a.depositar 10
puts a.saldo
if a.extraer 15 then
   puts "Extracción realizada"
else
   puts "Extracción superior al saldo"
end
puts a.saldo

Hemos definido una clase "Cuenta", que tiene los métodos "initialize", "saldo", "depositar" y "extraer". Luego hemos creado una instancia (objeto) "a" y lo hemos utilizado.

La salida de este programa es:

20
30
Extracción realizada
15

Examinemos este ejemplo:

  • Las clases se nombran con el primer caracter en mayúsculas ("Cuenta"). En caso de usar varias palabras, se utiliza la notación "camello" (por ejemplo "CuentaCorriente").
  • Los métodos y variables se nombran en minúsculas.
  • Las variables cuyo identificador comienza con "@" son "variables de instancia" o atributos (por ejemplo, "@saldo").
  • El método "initialize" de una clase, se invoca al crear una instancia de la misma. La creación de un objeto se realiza llamando al método "new" de la clase en cuestión, y los parámetros que éste reciba, serán pasados a "initialize" (por ejemplo, "Cuenta.new 20").

Ruby también permite variables y métodos de clase. Por ejemplo, podríamos modificar nuestra clase "Cuenta" de la siguiente forma:

class Cuenta
 
  @@cuentas = 0
 
  def self.cuentas 
    @@cuentas
  end
 
  def initialize(saldo_inicial)
     @saldo = saldo_inicial
     @@cuentas += 1
  end
 
  ... 
 
end

Hemos definido una variable de clase "@@cuentas", que será compartida por todas sus instancias (objetos). Además, hemos definido un método de clase "cuentas" (nótese la palabra "self" en la declaración, que indica que el método pertenece a la clase y no a sus instancias). Luego, podemos invocarlo directamente en la clase "Cuenta". Por ejemplo, si hiciéramos lo siguiente:

a = Cuenta.new 20
b = Cuenta.new 50
 
puts Cuenta.cuentas

Obtendríamos como salida "2".

Existe una serie de convenciones que simplifican la comprensión de los programas escritos en Ruby. Por ejemplo:

  • Si un método toma un valor y lo asigna a un atributo (comúnmente llamado "setter"), ese método debería llamarse igual que el atributo, con el agregado del caracter "=". Esto, sumado a la posibilidad de omitir los paréntesis, hace que la invocación de ese método parezca una asignación (como vimos en los ejemplos anteriores).
  • El nombre de un método que devuelve un valor lógico, debería terminar con el caracter "?". (Podríamos implementar un método "puede_extraer?" en la clase "Cuenta" y luego escribir "a.puede_extraer? 40".)
  • El nombre de un método que modifica el objeto sobre el que es invocado, debería finalizar con "!". (Un ejemplo de esto son los métodos "sort" y "sort!" de la clase "Array". El primero devuelve el arreglo resultante de ordenar el arreglo original, en tanto que el segundo ordena a este último.)

Ruby y las "clausuras"

Las "clausuras" son mecanismos que nos permiten pasar bloques de código como parámetros en la llamada a una función. En dichos bloques, podemos hacer referencia a identificadores definidos en el contexto (alcance o "scope") en el que se realiza la llamada. Veamos un ejemplo.

La clase "Array" provee un método "each" que recibe como parámetro un bloque y lo ejecuta para cada elemento del arreglo. El siguiente código:

saludo = "Hola "
a = ["Gabriel", "Pablo", "Javier"]
a.each{i puts saludo + i}

Produce la siguiente salida:

Hola Gabriel
Hola Pablo
Hola Javier

La expresión "i" establece la ligadura de el identificador "i" con cada uno de los elementos del arreglo "a". Luego, el bloque se ejecuta para cada uno de los elementos de este último. Nótese que, en el contexto de arreglo "a" no existe el identificador "saludo", sin embargo, el bloque es pasado como parámetro incluyendo las ligaduras al contexto en el que fue definido. Esta es la "magia" de las clausuras.

Otro ejemplo: supongamos que tenemos un arreglo "c" de objetos de clase "Cuenta" y deseamos calcular la suma de todos los saldos. Para ello, podemos hacer lo siguiente:

sumatoria = 0
c.each{i sumatoria += i.saldo}
puts sumatoria

Otro método de "Array" que soporta bloques como parámetros es "select", que devuelve sólo aquellos elementos para los cuales el bloque devuelve "true". Veamos otro ejemplo:

d = c.select{i c.saldo > 1000}

Esto selecciona (filtra) de "c" aquellas cuentas con un saldo mayor a 1000 y asigna el arreglo resultante a "d". Nótese cómo el texto del programa y su lectura (aún en castellano) son bastante similares. (Más adelante hablaremos sobre "expresividad".)

Clases "opensource"

Cualquier lenguaje de implementación "abierta" nos ofrece la posibilidad de modificar su librería estándar de funciones. En Ruby esto es trivialmente simple, ya que si ya hemos definido una clase "MiClase", una declaración del tipo "class MiClase ... end" añadirá las declaraciones introducidas a la definición ya existente.

Por ejemplo, si estamos acostumbrados a usar los métodos "head" y "tail" que devuelven, respectivamente, el primer elemento de una lista y el resto, los extrañaremos en la clase "Array" de Ruby. Pero, afortunadamente, podemos escribir la siguiente definición:

class Array
  def head
    self[0]
  end
  def tail
    slice(1..-1)
  end
end

Con esto, hemos añadido los métodos "head" y "tail" a la clase "Array" y, por lo tanto, pueden ser invocados en cualquier otra clase que la utilice. De esta forma es que las clases de Ruby son totalmente abiertas, ya que en cualquier lugar de nuestro sistema en donde se requiera, podemos extenderlas o modificarlas.

Expresividad

Por "expresividad" entendemos la posibilidad de expresar "claramente" mediante el código de un programa qué es lo que este realiza. Todos sabemos que algunos lenguajes, aunque quizás muy eficientes, son extremadamente "oscuros", y requieren de un buen rato estudiando cada línea y cada construcción para averiguar exáctamente qué hace un programa y cómo lo hace (al extremo en que podríamos hablar de lenguajes de "sólo escritura").

Ruby, a través de sus construcciones, nos ofrece la posibilidad de programar de forma "declarativa", esto es, que el código de nuestros programas resuelva el problema y, a la vez, sirva de especificación del mismo.

Tomemos como ejemplo el algoritmo de ordenamiento "quicksort". En su forma más simple, este algoritmo podría enunciarse como sigue:

Se toma el primer elemento de la lista a ordenar, se ubican a su izquierda los valores menores que éste, a su derecha los mayores, y luego se aplica recursivamente el mismo algoritmo en ambas partes.

De una manera aún más sintética, podríamos decir que hacer "quicksort" de una lista es aplicar "quicksort" a los menores que el primer elemento, luego agregar este y finalmente "quicksort" aplicado a los mayores.

Implementar "quicksort" en lenguajes como Java, C#, PHP o Pascal requeriría de más de 50 líneas de código y su comprensión, para alguien que no conociera el algoritmo de antemano, no sería trivial. En Ruby, utilizando los métodos definidos anteriormente en "Array", podríamos escribir lo siguiente:

class Array
  def qs
    empty? ? [] : tail.select{e e<=head}.qs + [head] + tail.select{e e>head}.qs
  end
end

El método "empty?" devuelve "true" si y sólo si el arreglo está vacío. El operador de asignación condicional "a ? b : c" evalúa la expresión "a", si ésta es verdadera devuelve el valor "b" y en caso contrario, "c".

Habiendo aclarado esto, la lectura de nuestro "quicksort" ("qs") es muy simple: Si el arreglo es vacío, devuelve un arreglo vacío ("[]"), y en caso contrario, devuelve el resultado de aplicar "qs" a los elementos menores al primero ("head"), seguido de éste elemento, seguido del resultado de aplicar "qs" a los elementos mayores al primero.

Como podemos ver, la implementación de "quicksort" en Ruby es prácticamente su especificación. ¡Esto es expresividad!.

Programación divertida

20:11 ST: Talleres 2 -vs- Instituto 0

La expresividad de Ruby se combina con un principio llamado de "menor sorpresa". Esto significa que las cosas se llaman como uno esperaría que se llamen y funcionan de la manera en que uno esperaría que funcionen. Sumando ambos elementos, el resultado es un lenguaje en el que resulta "divertido programar".

La realidad nos muestra que muchas veces los programadores desperdiciamos la mayor parte del tiempo (y de nuestra energía), lidiando con particularidades de los lenguajes que utilizamos. Esto llega a ocasionar que muchos pierdan la pasión que en los primeros años sentían por esas largas horas (¿días? ¿noches?) escribiendo código. Ruby, en cierta forma, fue creado para tratar de evitar esta situación. En palabras de matz:

"Quería minimizar mi frustración mientras programaba, por lo tanto, quería minimizar mi esfuerzo al programar. Ese fue mi principal objetivo en el diseño de Ruby. Quería divertirme programando. Luego de liberar Ruby, cuando muchas personas en todo el mundo lo conocieron, dijeron que se sentían igual que yo."[2]

Enlaces útiles


Enlaces útiles

Referencias

Licencia

© Copyright 2007 Javier Smaldone

Creative Commons License

Este artículo se distribuye bajo una licencia
"Atribución-NoComercial-CompartirDerivadasIgual 2.5 Argentina" de Creative Commons.

1 comment:

Unknown said...

Me encanta la nota! está muy clara y los ejemplos están bárbaros. Me enteré de casualidad en un ST que era Ruby. Me gusta mucho la programación orientada objetos aunque no me dedique a ello. Creo que la convinación Ruby + Genexus sería una exploción. Me gustaría que pase... es mas estoy esperando que pase. Reintero: muy buena la nota.

Saludos

Claudia Audisio.-