Marco Antonio Manzo Bañuelos <amnesiac@unix-power.net>
24-Ene-2004
Cabe señalar que ésta no es una guía total para el aprendizaje del lenguaje, existen libros y extensos tutoriales los cuales se pueden consultar en línea o adquiriendolos en las librerías.
El primer capítulo abarca una introducción de ruby, y mención de sus características principales. El capítulo 2 muestra los tipos de datos en ruby y su uso mediante ejemplo sencillos en el intérprete interactivo. El capítulo número 3 comprende el manejo de condiciones y ciclos, asi como una introducción a la construcción de iteradores. El capítulo 4 nos muestra las virtudes de ruby en el uso del paradigma orientado a objetos como tal. Y por último, el capítulo número 6 nos muestra un panorama de como ruby puede ser utilizado para diversas tareas y su capacidad ante ellas.
Ruby es un lenguaje creado por Yukihiro <Matz> Matsumoto mailto:matz@netlab.jp bajo la licencia GPL con el fin de proporcionar un lenguaje moderno, 100% orientado a objetos1 que sea sencillo de aprender, codificar y mantener, así como presentar una estructura muy elegante.
Ruby hereda muchas funcionalidades y características de lenguajes tales como Perl, Lisp, C++, smalltalk entre otros, con esto quiere decir, que los usos primordiales entran donde estos lenguajes tienen cabe. Es útil para los scripts de tareas cotidianas, procesamiento de texto, tareas de administración de sistemas, en el web, creación de aplicaciones usando interfaces gráficas ( tk, gtk ), asi como aplicaciones cliente/servidor utilizando sockets.
En el mundo bibliográfico ruby cuenta con extensa documentación en su página principal, así como en otras externas, cuenta con diversos libros desde cookbooks, hasta libros de referencia y guias completas ( incluso escritas por el propio autor de ruby ). 2
Entre las principales características de Ruby podemos citar las siguientes:
Ruby esta en constante desarrollo así como todos aquellos lenguajes donde existe una gran comunidad de desarrolladores, lamentablemente debido a su desarrollo en Japón, es allí donde tomó aun mas fuerza que Perl y Python, mas sin embargo, despues de mas de 8 años de existir, se sigue expandiendo, y haciéndose favorito de muchos desarrolladores en el Occidente.
Actualmente ruby es utilizado en diversas utilerías en sistemas operativos, por ejemplo, en FreeBSD3 la utilería llamada portupgrade ( instalable desde el árbol de ports ), esta hecha completamente en ruby, asi como otros scripts de automatización de actividades en dicho SO.
En ruby cualquier tipo de dato, función, método, es decir, TODO es un objeto. Por lo tanto ruby presenta los conceptos de encapsulamiento4, herencia5, y polimorfismo6. Como en los otros lenguajes, una variable válida tiene que comenzar con un caracter ( o dash _ ), pero el caracter debe ser en minúscula y no necesita de algún signo para identificar el tipo de dato o identificador alguno.
Cada uno de estos objetos tienen elementos que los hacen únicos para su tratamiento o uso. Cada objeto contiene un id7, un tipo y una clase ( genralmente estos 2 ultimos campos son el mismo, eso es lo que identifica el tipo de dato del objeto ) a la que pertenece el objeto, tal es el caso del siguiente ejemplo:
num = 2;
num.class
>> Fixnum
num.id
>> 5
num.type
>> Fixnum
num = 123 # pertenece a la clase Fixnum, id 247
num = 123.2 #pertenece a la clase Float, id 67267522
string = ``ruby world'' #pertenece a la clase String, id 67679260
arr = [ ``a'', ``b,'' ,''c'' ] #pertenece a la clase Array, id 67653440
hash = { 'a' => 1, 'b' => 2 } #pertenece a la clase Hash, id 67614160
En irb probamos la teoría:
>> A = 1
=> 1
>> A.type
=> Fixnum
>> A = 2
(irb):20: warning: already initialized constant A
Un arreglo lo podemos declarar mediante su utilización directa, o llamando el constructor de su respectiva clase.
arreglo = [ 1, 2, 3 ]
arreglo = Array.new
arreglo = Array.new(2)
arreglo = []
arreglo = Array.[](1, 2, 3) #lo mismo que la primera
arr = [ 1, 2, [ ``a'', ``b'' ], 3 ]
El acceso y modificación a un arreglo es al igual que en los demás lenguajes de programación, por medio de índices.
a = [ 1, 3, 5 ]
puts a[0]
>>1
a[0] = 3
b = [ 1, 2, [ ``a'', ``b'' ], 3 ]
b[1] = 3
puts b[2][1]
>> ``b''
puts b[4]
>> ``nil''
puts b
>> ``12ab3''
a = [ 1, 2, 3, 4 ]
b = [ ``ruby, ``perl'', ``python'' ]
b = %w[ ruby perl python ] #equivalente
Al igual que en lenguajes como perl y python, los arreglos puden ser utilizandos como los TDA's pila8 y cola9 con sus respectivas funciones.
a = [ 1, 2, 3, 4 ]; puts "Array original: " + a.to_s; a.pop; puts "Despues pop: " + a.to_s; a.push( 1 ); puts "Despues de push: " + a.to_s;
Array original: 1234
Despues pop: 123
Despues de push: 1231
a = [ 1, 2, 3, 4 ] puts "Array original: " + a.to_s a.shift puts "Despues shift: " + a.to_s a.unshift( 5 ) puts "Despues de unshift: " + a.to_s
Array original: 1234
Despues shift: 234
Despues de unshift: 5234
Operadores:
arr1 = [ 1, 2, 3, 4 ]
arr2 = [ 5, 6, 7, 8]
a = [ arr1, arr2 ]
a.assoc( 6 )
>> a = [ 5, 6, 7, 8 ]
arr = [ ``a'', ``b'', ``c'' ]
arr.collect! { |x| x + 1.to_s }
>> arr = [ ``a1'', ``b1'', ``c1'' ]
arr = [1, 2, 3, 4, 5 ]
arr.delete( 1 )
>> arr = [ 2, 3, 4, 5 ]
arr.delete_at( 2 )
>> arr = [ 2, 3 , 5 ]
arr.delete_if { |x| x < 4 }
>> arr = [ 5 ]
arr = [ 1, 3, 5, 7, 9 ]
arr.eql?( [ 1, 2, 3 ] )
>> false
arr.empty?
>> false
arr.include?( 7 );
>> true
arr = [ 1, 2, nil, [ 3, 4 ], nil, 5 ]
arr.flatten!
>> arr = [ 1, 2, nil, 3, 4, nil, 5 ]
arr.compact!
>> arr = [ 1, 2, 3, 4, 5 ]
arr = [ ``a'', ``c'', ``c'', ``d'', ``b'', ``b'' ]
arr.sort
>> arr = [ ``a'', ``b'', ``b'', ``c'', ``c'', ``d'' ]
arr.reverse
>> arr = [ ``d'', ``c'', ``c'', ``b'', ``b'', ``a'' ]
arr.uniq!
>> arr = [ ``a'', ``b'', ``c'', ``d'' ]
arr.replace( [ 1 ,2 , 3 ,4 ] )
>> arr = [ 1 , 2 , 3, 4 ]
Un hash ( conocido también como arreglo asociativo ) es un conjunto de valores, de la forma key => value que tiene numerosas funcionalidades donde el arreglo sencillo no puede entrar.
h = { ``a'' => 1, ``b'' => 2, ``c'' => 3 }
h = Hash.new; #devuelve un hash vacio
h = Hash.new( ``valor default'' )
h = Hash[ ``a'' => 1, ``b'' => 2 ]
El acceso a alguno de los valores del hash, se hace mediante su llave.
h = { ``a'' => 3.75, ``b'' => 4.12, ``c'' => [ 2, 3 ] }
puts h[''a'']
>> 3.75
puts h[''c''][1]
>> 3
h[''a''] = nil
puts h[''d''] #Aqui a menos que le mandemos un valor default al crear el hash, nos devuelve nil
>> nil
Algunas de los métodos que tienen la clase Array estan implementadas tambien en los hashes, la diferencia pues, es que en hashes trabajaremos con llaves y valores.
h = { ``a'' => 1, ``b'' => 2, ``c'' => 3 }
h.default = ``default_value''
puts h[''f''];
>> ``default_value''
puts h.default
>> ``default_value''
h.clear
>> h = {}
h = { ``a'' => 5, ``b'' => 10, ``c'' => 20 }
h.fetch( ``c'' )
>> 20
h.delete( ``b'' )
>> h = { ``a'' => 5, ``c'' => 20 }
h.delete_if { | k, v | v < 10 }
>> h = { ``c'' => 20 }
h = { ``a'' => 100, ``b'' => 200, ``c'' => 300 }
h.empty?
>> false
{}.emtpy?
>> true
h.has_value?( ``300'' )
>> true
h.has_key?( ``f'' )
>> false
h.index( ``200'' )
>> true
h1 = { ``d'' => 1, ``a'' => 2 }
h2 = { ``e'' => 100, ``b'' => 200 }
h1.sort
>>[ [ ''a'', 2 ], [ ``d'', 1 ] ]
h2.invert
>> h2 = { 100 => ``e'', 200 => ``b'' }
h1.update( h2 )
>> h1 = { ``d'' => 1, ``a'' => 2, 100 => ``e'', 200 => ``b'' }
Una de las partes más importantes de un lenguaje, es el tratamiento de las cadenas, con ello nos permitimos manipular textos de cualquier índole, ya sea con funciones o utilizando de cierta manera expresiones regulares.
En ruby, las cadenas se manejan igual que en los demas lenguajes, y como recordaremos, al igual que los otros tipos de datos, una cadena también es un objeto, por lo tanto una cadena pertenece a una clase ( String ) la cual contiene métodos para su manipulación.
La clase String contiene ya métodos predefinidos para el trabajo con cadenas, es decir, muchas de las tareas que realizamos ya existen: conversiones, analizadores, entre otras.
Por ejemplo para separar una cadena delineada por espacios, en componentes léxicos ( tokens ) aplicamos el método split, el cual nos regresa un nuevo arreglo con dichos tokens:
cadena = "splitting with split"
cadena.split
>> [ "splitting", "with", "split" ]
"split, split, split".split(", ")
>> [ "split", "split", "split" ]
"splitting with split".split
>> [ "splitting", "with", "split" ]
"palabra mayúscula".capitalize
>> "Palabra mayúscula"
"Word".swapcase
>> "wORD"
"word".upcase
>> "WORD"
"WOrd".downcase
>> "word"
"cadena nula".length
>> 11
"cadena nula".size
>> 11
str = "cadena"
str << " nueva"
>> "cadena nueva"
En ruby, el acceso y asignación de subcadenas es posible con slices:
str = "Esta es una cadena"
newstr = str[13..18]; #newstr = str[13, 6]; o newstr = str[-6, 6]
>> "cadena"
str = "Esta es otra cadena"
newstr = str[/otra/]
#newstr = str[/o..a/]
#newstr = str[^\w{4}]
>> "otra"
str = "Ruby es dinámico"
str[/d(.+?)$/] = "poderoso"
>> "Ruby es poderoso''
str = "Mas ejemplos con ruby"
str.sub!(/e/, "3")
>> "Mas 3jemplos con ruby"
str = "Mas ejemplos con ruby"
str.gsub!( /e/, "3" )
>> "Mas 3j3mplos con ruby"
str = "Ruby perl python"
str.sub(/(\w+) (\w+)/, '\2 \1') #lo que en perl son las variables $1..$9
>> "perl Ruby python"
str = "The Ruby way"
str.index( /way/ )
#str.index("way'')
>> 10
str = "The Ruby way"
str.include?("python")
>> false
str = "Ruby regular expressions"
str.scan( /\w+/ )
>> [ "Ruby", "regular", "expressions" ]
str = "lame rot13 crypt"
str.tr( "A-Za-z", "N-ZA-Mn-za-m" )
>> "ynzr ebg13 pelcg"
re = Regexp.new('\w+')
#re = %r(\w+)
#re = Regexp.compile( .. )
str = "nueva cadena"
str =~ re
>> 0
Regexp::EXTENDED -> ignora espacios vacios o saltos de linea
Regexp::IGNORECASE -> no sensitivo al caso
Regexp::MULTILINE -> los saltos de línea son tratados como otro caracter
Las funciones en ruby son métodos que no pertenecen a una clase determinada, es decir que no necesitamos de un objeto para llamarlas.
Se declaran por medio de la palabra reservada def seguido del nombre de la funcion y opcionalmente el nombre de los parametros esperados:
def mifuncion puts "Has llamado a mifuncion" end mifuncion
Has llamado a mifuncion
Los parámetros son declarados en la cabecera de la funcion ( después del nombre ) de la siguiente manera:
def mifuncion2 ( val1, val2 ) sum = val1 + val2 puts "La suma de los valores " + val1.to_s + " y " + val2.to_s + " es: " + sum.to_s end mifuncion2( 3, 5 )
La suma de los valores 3 y 5 es: 8
Los condicionales en ruby son los ya conocidos, if y unless
if x == 3 then sentencia end if x != 8 then sentencia 1 else sentencia 2 end sentencia if x < 4;
unless x != 3 then sentencia end unless x == 8 then sentencia1 else sentencia2 end puts "help" unless num.is_a? Fixnum
El case en ruby, a diferencia de los demas lenguajes, toma diferentes caminos para evaluar cada uno de los casos, puede evaluar por equivalencia ( === ) o buscando algun patrón:
a = "hello world" case a when "hello" puts "Contiene hello" when "nada" puts "invalida" when /\w+/ puts "re match: " + $~.to_s else puts "default value" end
En este ejemplo, el case devuelve la tercera comparación, puesto que el patron /\w+/ es verdadero en la cadena evaluada.
Ejemplo de utilización el ciclo for:
list = %w[ a b c d e ] for x in list do puts x end for x in 0..list.size do puts x end
x = 0; while x < [ 1, 2, 3, 5 ].size do print "#{x} ->" x = x.succ end x = 0 until x == [1, 2, 3, 4, 5].size do puts x x = x + 1 end
La clase Array, String y Hash contienen ( y comparten ) metodos para la iteracion sobre sus elementos, algunos ejemplos se citan a continuación:
[ 1, 2, 3, 4 ].each { | a | print ``#{a} + `` }
>> 1 + 2 + 3 + 4
[ ``a'', ``b'', ``c''].reverse_each { | x | print ``#{x} `` }
>> c b a
[ 1, 2, 3, 4 ].each_index { | x | print ``#{x} `` }
>> 0 1 2 3
hash = { ``a'' => 1, ``b'' => 2, ``c'' => 3 }
hash.each { | k, v | print ``#{k} -> #{v} -- `` }
>> a -> 1 -- b -> 2 -- c -> 3 --
hash.each_key { | key | puts k }
>> a b c
hash.each_value { | value | puts value }
>> 1 2 3
``Ruby\nworld''.each { | x | puts x }
>> Ruby
>> World
``Ruby world''.each(' ') { | a | puts a }
>> Ruby
>> World
``Ruby example''.each_byte { | b | print ``#{ b } `` }
>> 82 117 98 121 32 101 120 97 109 112 108 101
Existen métodos para iterar por medio de rangos, citamos algunos ejemplos:
list = %w[ ab ac ad ae ] list.size.times do | a | puts a * a end 0.upto( list.size ) do | x | print x * x end for i in 0..4 puts i * i end
Muchos de los métodos que sirven para manipular arreglos, hashes, o los métodos para otro tipo de iteraciones ( y vaya que muchos ) estan basados en una función llamada yield. Esta función vista por primera vez parece un poco compleja y engañadora mas sin embargo su propósito principal es ejecutar un bloque de código mandando a ella el valor de la variable a utilizar en el bloque, ejemplos:
def iterador 0.upto(5) do |x| yield x end end iterador { |a| puts "yield yields:" + a.to_s }
def collect2 ( *arr ) newarr = [] arr.size.times do |a| newarr << yield( arr[a] ) end return newarr end newarr = collect2( "a", "b", "c" ) { | x | x + "1" } puts newarr
Las reglas básicas para la construcción de clases en ruby son sencillas:
class Books @@total = 0 def initialize( name, author, isbn ) @name = name @author = author @isbn = isbn @@total = @@total + 1 end def show print " Name: #{ @name }\nAuthor: #{ @author }\nISBN: #{ @isbn }\n" end def Books.totalbooks puts @@total end end perl = Books.new( "Programming Perl", "Larry Wall", "1-2-234234234" ) perl.show ruby = Books.new( "Programming Ruby", "Yukihiro Matsumoto", "2-3-1231233" ) ruby.show Books.totalbooks El codigo imprime: Name: Programming Perl Author: Larry Wall ISBN: 1-2-234234234 Name: Programming Ruby Author: Yukihiro Matsumoto ISBN: 2-3-1231233 2
Cuando hablamos de métodos hablamos de funciones que pertenecen a una clase, y que van a responder a una instancia de esa clase ( es decir un objeto ), cuando creamos métodos, muy a menudo requerimos del acceso a variables de instancia ( accessors ), lo cual podemos escribir de esta manera:
...
@name
@author
...
attr_reader :name, :author
@name = name
@author = author
attr_writer :name, :author
attr_accessor :var1, :var2
Como en todos los lenguajes de programación orientados a objetos, siendo el encapsulamiento y abstracción una de las principales características de ellos, contamos también con la capacidad de restringir el acceso a las variables o métodos de una clase:
class Usuario attr_reader :name, :email def initialize ( username, email ) @name = username @email = email setpass end def setpass puts "Password para: #{ @name }" @pass = gets.chomp end def showdata print " #{ @name }\n #{ @pass }\n #{ email }\n" end private :setpass end amnesiac = Usuario.new( "Amnesiac", "somewhere@world.com" ) amnesiac.showdata amnesiac.setpass En la ejecucion tenemos: Password para: Amnesiac asdf Amnesiac asdf somewhere@world.com class2.ex:26: private method `setpass' called for #<Usuario:0x806e9fc> (NameError)
La sobrecarga de operadores es muy sencilla en ruby, basta con utilizar el método alias:
alias suma +
alias resta -
...
#class Books ... #end class RubyBooks < Books @@rubybooks = 0 def initialize( name, author, isbn, press ) super( name, author, isbn ) @press = press @@rubybooks += 1 end def rubybooks puts @@rubybooks end end book = Books.new( "Object Oriented Perl", "Damian Conway", "3-2-11231231231231" ) r = RubyBooks.new( "Ruby in a nutshell", "Yukihiro Matsumoto", "1-2-23423423432", "O'Reilly" ) r.rubybooks Books.totalbooks r.show El codigo muestra lo siguiente: 1 2 Ruby in a nutshell Yukihiro Matsumoto 1-2-23423423432
Los modulo nos ayudan a sintetizar el trabajo organizando en pequeños bloques de construcción nuestro codigo, ademas de proveer la interfaz básica de creación de los mixins.
module HTMLParser
... #aqui va la declaración de las funciones/clases del modulo
end
require ``HTMLParser''
...
¿Qué pasa con la herencia múltiple en ruby ?
Bueno, como es sabido la herencia múltiple puede ser muy ambigua y puede provocar confusiones, pues bien ruby se maneja de una manera muy peculiar, por medio de los mixins, éstos son módulos que contienen declaraciones de diferentes clases, las cuales van a heredar a una clase hija, por ejemplo:
class Clase1
...
end
class Clase2
...
end
Los singletons en ruby se presentan en 2 sabores, lo que es el diseño de patrones y lo que son singleton methods. En el primer caso, los singletons en el diseño de patrones, son aquellos donde una clase solo puede tener una instancia, por ejemplo:
class Administrator private_class_method :new @@admin = nil def Admin.newadmin @@admin = new unless @@admin @@admin end end
En este caso al crear un nuevo objeto con nuestro nuevo constructor, vamos a ver que cada que creemos uno, el objeto va a seguir referenciando al mismo id del primero, es decir, siempre tendremos la misma instancia.
Los singleton methods son aquellos los cuales solo responden al llamado de una misma instancia, es decir, a la instancia a la que pertenecen.
@name = name
puts @name
file = File.new( ``httpd-access.log'', ``a'' )
file.puts ``[12:34:00] Hey! Alguien nos ataca!! ah no... mentira''
file.close
Y... la mejor forma de ver que tan poderoso es ruby, es utilizándolo. ;)
This document was generated using the LaTeX2HTML translator Version 2002-2-1 (1.70)
Copyright © 1993, 1994, 1995, 1996,
Nikos Drakos,
Computer Based Learning Unit, University of Leeds.
Copyright © 1997, 1998, 1999,
Ross Moore,
Mathematics Department, Macquarie University, Sydney.
The command line arguments were:
latex2html -no_subdir -split 0 -show_section_numbers ruby.tex
The translation was initiated by Marco Antonio Manzo on 2004-01-26