Este articulo sobre iteradores fue previamente publicado por James Edward Gray II

Un amigo mio me estuvo realizando algunas preguntas en general acerca de iteradores en algunos mails privados que estuvimos intercambiando.

¿Por qué existen los iteradores?

Primero, creemos algunos datos para poder jugar:

Ahora asumamos que queremos imprimir algunos nombres. Podemos usar el iterador each() para eso, sin index:

Eso no es tan diferente a un bucle (loop) pero imaginemos que queremos realizar un find() de algún nombre específico:

Tal vez nos gustaría ver cuales son los apellidos que existen en nuestros registros. Podemos lograrlo al map()ear (otro iterador) los nombres sólo a los apellidos y utilizando un helper simple:

Quizás sólo queremos trabajar con un cinjunto acotado de elementos. Podemos seleccionarlos (select()):

O podemos realizar un sort_by() bajo cierto criterio:

Ahora recordemos, en los lenguajes centrados en el uso de bucles tenemos una estructura de control para estos casos, y suele escribirse de esta forma:

Es necesario especificar todos los detalles cada vez que se desea encontrar un objeto en una lista, o extraer información de la misma. Se recorre el índice, se administra el array/hash/loquesea donde se guardan los datos extraidos y se corta el bucle al finalizar. Nota: En cada uno de los ejemplos de ruby expuestos más arriba hice foco en el objeto individual y en lo que necesitaba hacer con él. Los iteradores están manejando todo el trabajo repetitivo y aburrido por mi, permitiendome concentrame en la tarea principal.

Imagino que un lado malo podría ser que hay aprender cada uno de los nombres de los iteradores, y qué es lo que hacen en vez de un sólo bucle (loop). Ruby trata de disminuir el crecimiento de este pénsamiento al consolidar todos los iteradores en un sólo “mix-in” y usando ese mismo conjunto en los objetos standards, como Array y Hash. Aun mejor, lo único que se debe hacer es definir el método each, realizar un mix-in de Enumerable y obtener así los demás iteradores de manera “gratuita”. Básicamente, sólo los aprendes una vez y los usas en cualquier lugar.

¿Cómo se contruyen los iteradores?

Digamos que queremos construir una lista vinculada (LinkedList) en uby. Algo así:

Contruyamos rápidamente una rutina para poblarla con algunos datos:

Ahora escribamos el iterador each() para esta clase, para permitirle a los usuarios recorrer los valores. usaremos además un límite (limit) opcional también, ya que la lista puede ser muy larga:

Noten como usé el yield para pasarle los valores al bloque de código a medida que accedía a cada uno.

Veamoslo en acción:

Escribamos uno mas, el iterador find() (técnicamente podriamos usar un mix-in para obtenerlo sin necesidad de escribir el código, pero verlo es educativo):

Aca estoy utilizando el yield para saber si el usuario está interesado en este valor. Le paso el valor al bloque de código y espero a que devuelva verdadero o falso.

Podriamos utilizarlo para encontrar cual de los primeros 100 números de la serie fibonacci son dividibles por tres:

The Rubylation Network

Refactoring con Metaprogramacion

September 27th, 2006

Este articulo sobre metaprogramacion fue previamente publicado por Ola Bini

La metaprogramación reflexiva forma parte de la conciencia de los programadores desde hace tiempo. Se puede lograr en muchos lenguajes, de una manera u otra. Algunos lenguajes lo han adoptado mejor que otros, entre estos podemos destacar Lisp, SmallTalk, Python y Ruby. A partir de que Ruby se hizo más popular, la metaprogramación actualmente comienza a ser algo más comunente utilizado. La discusión sobre DSL’s (domain specific languages) esta asociada con la metaprogramación, implementar un DSL (en el mismo lenguaje, por supuesto) es muy difícil sin metaprogramación reflexiva.

Recientemente releí mi copia de Refactoring, y como es usual me asombré de cuán en tema estaba, y cuán fácil y útiles eran los trucos que describe. Pero, también empecé a pensar que faltaba algo. Refactoring esta orientado a la Programación Orientada a Objetos, pero fui más y más adelente en la Programación Orientada a Objetos, con DSL’s, metaprogramación reflexiva, instrospección, y extensiones Meta-class. Todo esto hace la base de POO mucho más poderosa. Esto es muy importante también cuando prototipamos pequeños sistemas. Encontré que escribiendo métodos de esa manera tenía que hacer mucho a mano, y en la siguiente instancia achique mi código lo más que pude para que quede claro y fácil.

¿De qué es este blog entonces? Propongo que sea un catálogo de Refactorings con Metaprogramación. No estoy diciendo que tienen que hacerlo, para nada, pero es algo bueno para conocer. Puede ser como un wiki en algun lugar, donde alguno de nuestros iluminados de la metaprogramación podrian escribir sobre sus experiencias. DHH? _why? Weirich? Dave Thomas?

Igualmente, solo para mostrar de que clase de refactoring estoy hablando, voy a dar algun tipo de ejemplo artificial. Esto es más o menos lo que sería una implementación de un mock o algo similar. Algunas llamadas al log, y bastante repetición.

Les presento el Extract Code Template Metarefactoring. El primer paso es tomar todos los nombres de los métodos que deberían ser manejados y ponerlos en una lista como ésta:

Entonces iteramos sobre estas definiciones, y proveemos estructuras vacías para cada uno de esta forma:

Entonces tenemos que cambiar la lista a un hash, haciendo que cada nombre de método apunte al correspondiente a llamar en cada caso. Algo así:

Cuando esto está terminado, tenemos que agregar la parte del método principal el cual no puede ser extraido de la misma manera, así que lo haremos con un Proc:

El siguiente paso es iterar sobre los nombres de los métodos y valores, y definir el contenido de los mismos. Entondes quitamos los métodos originales. Finalmente, nuestro código quedaría de la siguiente forma:

Ahora, en este caso, no estoy seguro si haría este refactoring. Esto sirve más como un ejemplo de que tipos de refactorings me gustaria encontrar en un catálogo como este. Refactorings como Extraer DSL (Extract DSL), Crear clases dinámicamente (Create Class Dynamically), Extender desde una clase Anónima (Extend From Anonymous Class) y otros, son algunos ejemplos de los que realmente creo pueden ser utiles en la programacion de hoy en dia.

The Rubylation Network

Este articulo fue anteriormente publicado por Pat Eyler sobre herramientas Test-Primero (Test-first). Fue republicado por Rubylation Network y esta disponible en otros lenguajes

Hoy en ruby-talk, encontre un hilo de conversacion llamado “Para obtener performance, escribe en C” (“For Performance, Write it in C”). Me gustaria presentar una pequeña enmienda a dicha tesis. Permitanme explicarles.

Todo comenzo con un email en mi trabajo. Alguien estuvo distribuyendo un generador de numeros primos en distintos lenguajes (C, Java, C#, Perl, and Python). Todos ellos usando el mismo (feo) algoritmo, supuestamente para mostrar cuan performante era cada lenguaje. Desde que soy el evangelista local de Ruby, fui solicitado para escribir la version Ruby. Aqui esta con lo que comence (cuidado, hay un feo algoritmo delante):

How fast is it? Well, time says: Cuan rapido es? Bien, los tiempos dicen:

$ time ./primes.rb > /dev/null

real    0m2.905s
user    0m2.716s
sys     0m0.004s
$

Ciertamente, nada para como para tomar nota, pero no tan lejos de Perl o Phyton tampoco.

Deseando mejorarlo, y no pudiendo modificar el algoritmo (deseamos comparar manzanas con mermelada a lo sumo, pero no manzanas con naranjas). Se que mi unica esperanzae es encontrar el/los cuello/s de botella y reescribirlo/s en C?. Mi primer paso es usar el Ruby profiler y ver que es lo que dice (oh, ya que estamos, reduje el valor de num a 100 asi esto puede completarse dentro de el ciclo de mi vida…. el profiler es lento)

$ ruby -rprofile primes.rb > /dev/null
  %   cumulative   self              self     total
 time   seconds   seconds    calls  ms/call  ms/call  name
 63.64     0.14      0.14      101     1.39     3.56  Range#each
 13.64     0.17      0.03     1133     0.03     0.03  Fixnum#%
  9.09     0.19      0.02     1233     0.02     0.02  Fixnum#==
  4.55     0.20      0.01      100     0.10     0.20  Kernel.puts
  4.55     0.21      0.01      200     0.05     0.05  IO#write
  4.55     0.22      0.01      248     0.04     0.04  Fixnum#to_s
  0.00     0.22      0.00       74     0.00     0.00  Fixnum#/
  0.00     0.22      0.00      100     0.00     0.00  Fixnum#-
  0.00     0.22      0.00        1     0.00   220.00  #toplevel
$

Lo cual me dice que la mayoria de mi tiempo se esta consumiendo en each (bien, actualmente consumido en el block que envie a each. Se esta tomando unos alegres 1.39 msec por llamada, comparados a .0X msec por el resto. Que deberia pasar si reescribo ese block?

Entra a RubyInline (Una gran herramiente escrita por zenspider and Eric Hodel). Yo no soy un guro en C y de ningun modo me podria calificar de experto, pero este es realmente facil de utilizar. Mi nuevo codigo queda asi:

No demasiado feo. Al menos aun se puede ver que esta pasando. El loop principal en Ruby ayuda mucho tambien (especialmente si este fuese un programa mucho mas grande). Cuanta diferencia hace esto? Veamos que dicen los tiempos:


$ time ./cprimes.rb > /dev/null

real    0m0.328s
user    0m0.288s
sys     0m0.020s

De acuerdo a la magnitud de la mejora. Nada despreciable.

Cual es la leccion aqui? Optimizar lo que necesites (y solo lo que necesitas), con profile encontrara que necesitas optimizar (puede ser lento, pero profiling es tu amigo), usa las herramientas correctas (reescribir una parte del codigo con RubyInline es mejor que reescribir toda la aplicacion en C).

Este articulo fue publicado el miercoles 6 de septiembre a las 23:00 hs. Disponible en otros lenguajes en The Rubylation Network

Ruby Central y Skill Matter tienen el placer de anunciar el Primer RailsConf Europeo 2006.

RailsConf estara ubicada en el TUC Congress Centre en Londres el 14 y 15 de Septiembre.

Oradores Confirmados:

y muchos mas seran anunciados!

Para mas informacion Railsconf Europe

Este articulo fue publicado el miercoles 12 de julio a las 23:00 hs. Disponible en otros lenguajes en The Rubylation Network

Este articulo fue anteriormente publicado por Pat Eyler sobre herramientas Test-Primero (Test-first). Fue republicado por Rubylation Network y esta disponible en otros lenguajes

El enfoque de esta columna cubre en profundidad las herramientas de desarrollo basadas en pruebas (del inglés Test-First Development, Test-Driven Development, o desarrollar primero con pruebas, desarrollo guiado por pruebas). Si usted no está familiarizado con este modelo de desarrollo y desea aprender más, aquí tiene algunos buenos recursos:

  • XP Magazine
  • mi artículo en IBM Developerworks
  • Mi anterior articulo en ZenTest

Prefiero escribir código usando este modelo ya que me siento más seguro sobre lo que escribo. Con este modelo, puedo obtener rapidamente una implementacion funcional y puedo re-estructurar facilmente todo para un mejor diseño. No es dañino que Ruby provea agradables herramientas para el trabajo de acuerdo con los principios de Probar Primero, o que algunas buenas herramientas esten disponibles para Ruby.

Mi caja de herramientas para Probar Primero incluye Test::Unit, rake, rcov, autotest y unit_diff. Las dos primeras deben ser muy familiares para la mayoria de los hackers de Ruby. Si aún no las conocen, Test::Unit se encuentra bien documentada en el libro Pick Axe y en www.ruby-doc.org. Puede leer mas sobre rake en mi articulo en IBM developerworks, y en este articulo por Martin Fowler.

Si todavia no usas Test::Unit y rake, haz un timepo para aprender a usar estas excelentes herramientas. Test::Unit es distribuido con Ruby y rake esta disponible en formato rubygem en RubyForge.

En lo que respecta a las otras herramientas en mi caja, rcov es una herramienta de analisis de cobertura de código. Cuando es ejecutada en conjunto las pruebas, genera un analisis sobre las llamadas en el codigo implementado. rcov se encuentra disponible en eigenclass.org. Usted puede generar HTML – mira este ejemplo – O una representación ASCII, en forma truncada como se muestra abajo. rcov trabaja de manera rápida; correr un programa con rcov es solo 2 o 3 veces más lento que el tiempo de ejecución normal. Adicional, rcov produce buenos y utiles resultados.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20


class MockDB | 2
                                                                      |      0
  def exec(query, &block)                                             |     11
    case query.split(' ')[3]                                          |      5
    when 'zero'                                                       |      5
      num = 0                                                         |      1
    when 'one'                                                        |      4
      num = 1                                                         |      1
    else                                                              |      0
      num = 2                                                         |      3
    end                                                               |      0
                                                                      |      0
    yield [num]                                                       |      5
                                                                      |      0
  end                                                                 |      0
                                                                      |      0
end      
                                                             |      0

Puedes ejecutar rcov en conjunto con un suite de pruebas como este:

1
2

$ rcov test/test_hostname

Adicional, incluye estas opciones de linea de comandos:

  • -t: genera resultados en texto sin formato.
  • -T: genera resultados en texto sin formato, pero mejorado.
  • -p: genera resultados de optimizacion (profiling).
  • -x: excluye archivos, este parametro toma opciones como expresiones regulares separadas por comas.
  • —no-html: no genera archivos HTML.

Si optas por generar resultados en texto sin formato, puede ser de utilidad redireccionarlos a un archivo. O, querrás usar ‘tee’ para un volcado a archivo—los resultados de rcov pueden ser muy extensos.

Aunque normalmente las herramientas de cobertura son empleadas para mostrar donde usted necesita escribir mas pruebas, recientemente tuve una experiencia en la cual rcov me llevo a una re-estructura (refactoring). Habia estado escribiendo código por un par de horas para comprobar el hostname. Decidí hacer una pausa y ver como cuan buena era mi cobertura de codigo. Esperaba que fuera 100%, pero muchas veces es agradable ver esto probado. Asi que me sorprendí cuando ví una banda roja al final de una banda verde. Algo no estaba siendo probado!

Revisé mi código y mis aseveraciones (assertions). Yo podia ver donde realizaba la prueba que fracasaba, pero rcov no me creía. Resultó que un chequeo que habia implementado posterior a este duplicaba mi prueba, de tal manera que mis fallas eran atrapadas antes de que siquiera llegara hasta ahi. Tuve que separar mi metodo de comprobación o eliminar el codigo sin uso. Gracias a rcov, mi código terminó siendo un método más pequeño.

La versión actual de rcov tiene un pequeño bug del cual deberán tener cuidado. Este no busca por continuaciones de linea despues de “and” o “or”. Esto requiere un parche de una sola linea en el codigo de rcov y Mauricio lo pondrá en la próxima versión.

De regreso a las otras herramientas en mi caja, autotest y unit_diff son distribuidos con ZenTes, el cual esta disponible como un rubygem. Ambos estan pensados para ayudar a alivianar las pruebas continuas en su rutina.

Con tu codigo en ./lib y sus pruebas en ./test, autotest succiona los archivos de prueba, los ejecuta y muestra los resultados. Genera un mapa entre las pruebas y los archivos de implementación, clases y metodos. Cada vez que usted guarda un archivo que esta en este mapa o crea uno que se agrega a esta lista, autotest re-ejecuta las pruebas. En el momento en que un test falla, autotest ingresa en un ciclo mas cerrado, ejecutando solamente la prueba que falla. Esto permite que usted se enfoque inmendiatamente en corregir el codigo fallante antes de proseguir.

Me tomo un par de horas de trabajo con autotest antes de que tomara el ritmo de este. Las primeras cosas que hice fueron pequeñas tareas – Actualicé un par de pruebas para Ruby 1.8.4 – de tal manera que autotest no tenia tiempo de completar su ciclo, buscar cambios y ejecutar el ciclo de pruebas nuevamente. De cualquier manera, autotest tiene una forma de lidiar con esto; presionando Ctrl-C una vez re-ejecuta las pruebas inmediatamente. Presionando la combinacion de teclas dos veces finaliza autotest.

Tambien descubrí que autotest no gusta de las pruebas que fallan al ejecutarse. Cuando estoy escribiendo codigo con Pruebas Primero, al principio de mi ciclo de desarrollo, normalmente requiero (‘require’) un archivo de implementación que no existe. Durante un desarrollo normal, es probable que incluya algun error de sintaxis o una letra. Cualquiera de estos, resulta en autotest mostrando el error generado por Ruby y luego la siguiente linea:

1
2
3
4


# Test::Unit murió, usted hizo algo realmente malo, reintentando en 10

No es lo mejor para tu ego, pero es una manera descriptiva de alzar una bandera roja y mantenerte encaminado.

unit_diff es otro pequeño programa que rapidamente se torna invalorable. Este lleva las porciones resultantes de una aseveracion fallante, lo “esperado” y lo “recibido”, empleando diff, por ende reduciendo largos bloques de texto a algo mucho mas manejable. El autor de unit_diff, Eric Hodel, dice que el programa fue escrito para ayudar con el desarrollo de ParseTree, donde una aseveración con fallas podia producir largas pantallas dificil de leer. Unit_diff convierte esta pesadilla en dos o tres lineas que muestran el error exacto. Sumado, descubrí que es invalorable cuando se trabaja con resultados en XML.

Es facil de ejecutar unit_diff. Simplemente redirecciona sus pruebas normales mediante él, de esta manera:

1
2

$ ruby test/test_hostname | unit_diff

Cualquier error que este encuentre será capturado y mostrado apropiadamente.

Espero hayas disfrutado este pequeño recorrido sobre las herramientas que ofrece Ruby sobre Test-Primero (Test-First development tools). Volvere pronto a ahablar acerca de mas temas de Ruby. Si quieres que cubra algun tema en particular, por favor sientete libre de dejar un comentario aqui.

Este articulo fue publicado el miercoles 12 de julio a las 23:00 hs. Disponible en otros lenguajes en The Rubylation Network