Category: ruby


Ruby: gmaps-geocode 0.1.0 is out !!

July 4th, 2009 — 09:12 pm

Disponible en github

Comment » | ruby

Ruby: ¡Cállate IRB!

June 22nd, 2009 — 10:45 am

¿Harto de que en alguna ocasión IRB sea demasiado verboso? Yo si.

Por defecto, IRB siempre “escupirá” el valor resultante de evaluar la expresión introducida por el usuario. Aunque la mayoría de las veces eso está bien, hay otras en las que sacarias el corazón con una cucharilla de Té  a quien tuvieras delante por la crispación que produce esta “verbosidad”.

Me refiero a situaciones del tipo “quiero recorrer un array y listar el nombre de sus elementos con puts”, ejemplo:

ArrayEnorme.each do |element| puts "#{element.name}\n" end

Con el código anterior vamos a mostrar el nombre de cada elemento del Array. Hasta aquí todo normal. El problema de evaluar esto en IRB es que, si ArrayEnorme tiene bastantes elementos y cada elemento tiene bastantes atributos, las lineas que imprimos con el nombre de cada elemento quedaran “sepultadas” por un montón de datos inutiles. Es decir, si el buffer de nuestra consola no se ha llenado con estos datos inutiles (perdiendo así las lineas con el nombre de cada elemento) tendremos que hacer scroll ad-infinitum, que para el caso es casí como si se hubierán perdido.

Para evitar esto, os presento mi solución de andar por casa:


def toggle_output
context.return_format == "Output canceled\n" ? IRB.conf[:PROMPT][context.prompt_mode][:RETURN] : context.return_format = "Output canceled\n"
end

alias tgo toggle_output

Después de copiar las lineas anteriores en nuestro .irbrc podemos hacer pruebas:

madtrick::madtrick::$irb
irb(main):001:0> 2 + 1
=> 3
irb(main):002:0> toggle_output
Output canceled
irb(main):003:0> 2 + 1
Output canceled
irb(main):004:0> toggle_output
=> "=> %s\n"
irb(main):005:0> 2 + 1
=> 3

Y voila, ya podemos callar y dar voz a IRB cuando nos plazca.

2 comments » | programacion, ruby

Ruby: Sketches en IRB

June 19th, 2009 — 05:47 pm

Hoy descubrí una gema m-a-r-a-v-i-l-l-o-s-a: Sketches.

El concepto que implementa sketches es sencillo: las modificicaciones que hagamos en nuestro editor de texto, se reflejan al instante en la sesión de irb. Es decir, si en el editor de texto creamos una clase y guardamos los cambios, la clase estará disponible automaticamente en la sesión de irb.

Su utilización es sencilla: arrancamos el IRB y lanzamos nuestro editor favorito con el comando sketch. A partir de ahí, el funcionamiento es como expliqué antes.

Mola : )

Comment » | programacion, ruby

Ruby: callcc

June 3rd, 2009 — 04:04 pm

callcc, es la abreviatura de call-with-current-continuation, algo así como un goto con esteroides.

En el rdoc nos encontramos con la siguiente definición:

callcc {|cont| block } => obj

Generates a Continuation object, which it passes to the associated block. Performing a cont.call will cause the callcc to return (as will falling through the end of the block). The value returned by the callcc is the value of the block, or the value passed to cont.call. See class Continuation for more details. Also see Kernel::throw for an alternative mechanism for unwinding a call stack.

Un tanto críptico, ¿No?. Basicamente, callcc almacena la dirección de memoria y el contexto (en un objeto Continuation) de donde se le llamo. Sigue siendo raro,¿No?

Mejor un ejemplo. Sea el siguiente metodo:

 
counter = 0
 
def foo
exit if callcc{|$cont| } == 3
puts "hello world"
end
 
foo
 
$cont.call(counter += 1)

(Si ya se que las variables globales son malignas : ( )

Ejecutando el ejemplo:

madtrick::madtrick::$ruby test3.rb
hello world
hello world
hello world

Como se puede apreciar, se imprimen tres “hello world” con sólo una llamada al método foo.

¿Qué podemos extraer del ejemplo anterior?

  • Cada llamada a la continuación ($cont) nos vuelve a llevar a linea donde se definió.
  • Si en la llamada a la continuación pasamos algun parámetro, este es devuelto por callcc

Tambien podríamos utilizar los ejemplos del post anterior (Tail Call Optimization) para explicar el funcionamiento de callcc:

Calculo del factorial de un número:

class TCOTest
  # tail-recursive factorial
  def fact( n, acc = 1 )
    if n < 2 then
      acc
    else
       fact( n-1, n*acc ) 
    end
  end
 
  def fact_cc( n, acc = 1 )
    cont = callcc{|cont| cont}
    if n < 2 then
      acc
    else
       n, acc = n-1, n*acc
       cont.call cont 
    end
  end
 
  # length of factorial
  def fact_size( n )
    fact( n ).size
  rescue
    $!
  end
 
 
  # length of factorial
  def fact_size_cc( n )
    fact_cc( n ).size
  rescue
    $!
  end  
end

Si lo probamos en el irb vemos que fact_size_cc no da ningún tipo de problema mientras que la función fact_size (sí, por carecer Ruby de TCO) ya que nos quedamos sin espacio en la stack.

...
irb(main):009:0> t.fact_size 10000
=> #<SystemStackError: stack level too deep>
irb(main):010:0> t.fact_size_cc 10000
=> 14808

El otro ejemplo del post anterior, la función de Fibonacci:

En el ejemplo anterior, utilizábamos la palabra reservada redo junto con un método definido ad-hoc para poder realizar el cálculo:

  define_method(:acc) do |i, n, result|
    if i == -1
      result
    else
      i, n, result = i - 1, n + result, n
      redo
    end
 
def fib_redo(i)
   acc i,1,0
end

Pero tambien se podria haber hecho de esta otra forma:

def fib_call_cc(i, n = 1, result = 0)
  cont = callcc{|cont| cont}
  if i == -1
    result
  else
    i, n, result = i - 1, n + result, n
    cont.call cont
  end
end

En el ejemplo anterior se puede apreciar como el contexto se mantiene. En este caso para los valores i,n y result.

Si ejecutamos un simple benchmark (para n en 50000) para comparar redo vs callcc, obtenemos los siguiente:

 
madtrick::madtrick::$ruby test.rb 
Time for fib_redo:      0.390000   0.010000   0.400000 (  0.403986)
Time for fib_call_cc:   0.500000   0.000000   0.500000 (  0.519504)

redo ( 0.403986) es un poco más rápido que callcc ( 0.519504). Echándole un vistazo rapido al codigo de la máquina virtual de Ruby se entiende rápidamente el porque. redo esta implentado utilizando goto’s mientras que callcc es una llamada a una función que se encarga de guardar toda la información necesaria en un objeto Continuation, por lo que necesita más tiempo.

Para mas información sobre callcc, su funcionamiento y los continuations:

Comment » | programacion, ruby

Ruby: TCO (Tail Call Optimization)

June 1st, 2009 — 11:21 pm

Leyendo el libro de Programming Erlang descubrí un concepto que no conocia: funciones tail-recursive.

En esta pagina lo explican bastante bien (entre otros tipos de recursividad), pero basicamente una función tail-recursive es aquella que no tiene ninguna operación pendiente de ejecutar tras la llamada recursiva. En estas funciones, la llamada recursiva puede ser substituida simplemente por un salto al comienzo de la función que se llama, con lo que se ahorra espacio en el stack. De esto ultimo se encarga el compilador y es lo que se conoce como Tail Call Optimization (TCO a partir de ahora).

Así que aparentemente la TCO mola, ¿No?

El problema es que Ruby no tiene implementada TCO en su maquina virtual con lo que independientemente de como estructuremos nuestras funciones recursivas, siempre estaremos limitados por el tamaño de nuestra Stack.

Pero siempre hay solución para todo y con Ruby, más. En ésta página dan dos soluciones para tener una pseudo-TCO.

Copio y pego la primera solución:

class Class
  # Sweet stuff!
  def tailcall_optimize( *methods )
    methods.each do |meth|
      org = instance_method( meth )
      define_method( meth ) do |*args|
        if Thread.current[ meth ]
          throw( :recurse, args )
        else
          Thread.current[ meth ] = org.bind( self )
          result = catch( :done ) do
            loop do
              args = catch( :recurse ) do
                throw( :done, Thread.current[ meth ].call( *args ) )
              end
            end
          end
          Thread.current[ meth ] = nil
          result
        end
      end
    end
  end
end
 
class TCOTest
  # tail-recursive factorial
  def fact( n, acc = 1 )
    if n < 2 then acc else fact( n-1, n*acc ) end
  end
 
  # length of factorial
  def fact_size( n )
    fact( n ).size
  rescue
    $!
  end   
end
 
t = TCOTest.new
 
# normal method
puts t.fact_size( 10000 )  # => stack level too deep
 
# enable tail-call optimization
class TCOTest
  tailcall_optimize :fact
end
 
# tail-call optimized method
puts t.fact_size( 10000 )  # => 14808

Esta solución consiste en jugar con catch’s,throw’s y Thread.current para relanzar el mismo método una y otra vez sin tener que crear un nuevo frame en el Stack, ya que el codigo del metodo se encuentra definido en un bloque y por tanto reutiliza el codigo.

La segunda opción:

def fib(i, n = 1, result = 0)
  if i == -1
    result
  else
    i, n, result = i - 1, n + result, n
    redo
  end
end
 
fib(10000)

Esta solución consiste en hacer uso de la palabra redo la cual reinicia la ejecución de cualquier loop o iterador. El problema con redo es que, como acabo de decir, solo se puede utilizar en loops o iteradores y el método fib anterior no es ninguno de ellos. Por lo que el autor de la página de que se extrajeron las soluciones dice:

Unfortunately, “The redo statement restarts the current iteration of a loop or iterator”, so it only throws a LocalJumpError

ruby_spartan1

Gracias señor espartano.

Así es, esto es Ruby y siempre hay un workaround para todo. En este caso, consiste en crear un nuevo método cuyo cuerpo este contenido en un bloque.

define_method(:acc) do |i, n, result|
  if i == -1
    result
  else
    i, n, result = i - 1, n + result, n
    redo
  end
end
 
def fib(i)
  acc(i, 1, 0)
end
 
fib(10000)  # Yeah!

Y ya podemos utilizar redo. Fin!!

Para mas información, hay un thread en esta lista de correo en el que debaten sobre este tema: Ruby tail recursion.

Comment » | programacion, ruby

« Previous Entries