domingo, 28 de diciembre de 2008

Crear demonios en ruby

Para crear un demonio hay que duplicar el proceso actual (hacer un fork) y distinguir a cada una de las instancias para la que instancia hija se encargue del trabajo en background que queremos realizar.

En ruby todo este proceso se puede realizar automáticamente utilizando el módulo ruby daemon de la siguiente forma.

require 'daemons'

class Demonio
def initialize
Daemons.daemonize
loop do
f = File.new('/tmp/timestamp', 'a')
f.write("#{Time.now}\n")
f.close
sleep(3)
end
end
end

Demonio.new

viernes, 26 de diciembre de 2008

Redefinir el método de comparación con expresiones regulares

Para mejorar la búsqueda en los datos de los objetos se puede aprovechar que en ruby se puede redefinir el método de comparación con expresiones regulares (=~).

Un ejemplo.

class Persona
attr_accessor :nombre, :apellidos, :direccion

def initialize(nombre, apellidos, direccion)
self.nombre = nombre
self.apellidos = apellidos
self.direccion = direccion
end

def =~(re)
"#{self.nombre} #{self.apellidos} #{self.direccion}" =~ re
end

def self.search(ciudad, input)
query = Regexp.new(input, 'i')
ciudad.select{|p| p =~ query}
end
end



p1 = Persona.new('Jose', 'Gonzalez', 'Sol')
p2 = Persona.new('Francisco', 'Martinez', 'Preciados')
p3 = Persona.new('Fernando', 'Solano', 'Serrano')

Madrid = [p1, p2, p3]

puts Persona.search(Madrid, 'sol').map(&:nombre)

El modificador 'i' del constructor de expresiones regulares hace que no distinga entre mayúsculas y minúsculas.
Concatenando los campos en los que queremos buscar se obtiene un matching más completo.

Con el map final se imprimen los nombres de las personas que hayan coincidido con la búsqueda.

domingo, 14 de diciembre de 2008

Recuperación automática de procesos erlang

La mayor ventaja que proporciona erlang es la gran potencia que tiene para la programación de procesos concurrentes.

Cuando se diseña software que va a ser programado en erlang suele dividirse el trabajo en múltiples procesos, además provee de mecanismos para comprobar fácilmente entre procesos si alguno se ha colgado y restaurarlo o ejecutar cualquier otra acción.

En el siguiente ejemplo.

-module(lib_misc).
-export([link_p/1, start/0, loop/0]).

start() ->
Pid = spawn(fun loop/0),
register(divisor, Pid),
spawn(lib_misc, link_p, [Pid]),
Pid.

loop() ->
receive
{A, B} ->
io:format("~nDivision: ~p", [A/B]),
loop()
end.


link_p(Pid) ->
process_flag(trap_exit, true),
link(Pid),
receive
{'EXIT', Pid, _} ->
NewPid = spawn_link(lib_misc, loop, []),
register(divisor, NewPid),
link_p(NewPid)
end.

Al compilar la librería y arrancarla se ejecutan dos procesos, el principal loop y otro llamado link_p.

El bucle principal simplemente recibe una tupla con dos números y los divide. Tiene al menos un error conocido y es que no comprueba cuando el divisor es cero.

El proceso link_p se enlaza al principal, la tarea de este proceso es esperar a que el principal se cuelgue (por ejemplo, división entre cero).
En el momento en el que el programa principal se ha colgado, recibe el mensaje de salida, restaura el proceso y se enlaza al nuevo.

Hay un detalle más, al iniciar un nuevo proceso el pid del antiguo no nos sirve para mandarle mensajes, así que se registra el átomo divisor para tener siempre una referencia global.

Registrar un nombre global estático no es la mejor solución, ya que no se pueden iniciar múltiples instancias del proceso divisor, debería ser un parámetro, lo he dejado así por que el ejemplo fuera más sencillo.

lunes, 8 de diciembre de 2008

Funciones anónimas recursivas en Erlang

En erlang se pueden definir funciones anónimas con la palabra reservada fun, aunque se pueden guardar en una variable para referenciarlas posteriormente.

La sintaxis es la siguiente:

IsZero = fun(0) -> 1; (N) -> 0 end.

Esta función devuelve 1 (verdad) si el parámetro es 0, falso en cualquier otro caso.

El problema es que de esta forma no se pueden definir funciones recursivas, ya que al no estar definida la función dentro de su definición, no podemos referenciarla, para solucionarlo se puede pasar un parámetro extra, que sea la propia función.

Factorial = fun(1, F) -> 1; (N, F) -> N * F(N-1, F) end.

En este último caso habría que llamar a la función como Factorial(número, Factorial), lo que queda poco intuitivo, se puede mejorar creando dos funciones en lugar de una.

Fact = fun(1, F) -> 1; (N, F) -> N * F(N-1, F) end.
Factorial = fun(N) -> Fact(N, Fact) end.