Paradigmas de programación con Ruby

Cuando hablamos de paradigmas de programación con Ruby nos referimos a un enfoque a resolver problemas. Existen diversos enfoques, los dos mas comunes son: imperativo y declarativos.

Imperativo

En el enfoque imperativo un programador escribe paso a paso como resolver el problema dando especial énfasis al estado de las variables y sus cambios, muchos de los lenguajes más famosos ocupan este enfoque, ya sea C, C++, Ruby, Python, PHP, Java.

La programación orientada a objetos extiende el enfoque imperativo agregando un fuerte enfoque hacia modularidad y reutilización de código.

Declarativo

En el enfoque del paradigma declarativo nos preocupamos de los resultados en lugar del proceso, el ejemplo más conocido es SQL, donde simplemente seleccionamos los valores que necesitamos y es el motor de SQL quien resuelve como buscarlos. Pero existen muchos otros lenguajes, una subcategoría muy importante de los declarativo son los lenguajes funcionales.

Funcional

La programación funcional consiste en un acercamiento matemático en resolver problemas donde sistemáticamente se aplican funciones para llegar al resultado final. Ejemplos como este Haskell, Erlang y Scheme.

Programación funcional en ruby

Ruby es un lenguaje multiparadigma, o sea se puede trabajar de forma imperativa y funcional.

Para trabajar bajo un acercamiento funcional lo primero que necesitamos son funciones y también necesitamos que nuestras funciones puedan recibir otras funciones, para lograr esto en ruby tenemos que (recordar || aprender) que en ruby no existen las funciones como tal, existen los métodos, pero cuando los métodos se definen bajo main no tienen mucha diferencia con una función en otro lenguaje.

[symple_box color=»black» fade_in=»false» float=»center» text_align=»left» width=»»]
def suma(a, b)
return a + b
end

#suma(2, 3) => 5
[/symple_box]

Flexibilizando los métodos con bloques

Los métodos además pueden recibir bloques (en cierto sentido son muy similares a una función), con yield ejecutamos el bloque dentro del método.

[symple_box color=»black» fade_in=»false» float=»center» text_align=»left» width=»»]
def suma(a, b)
yield
return a + b
end
suma(2, 3){puts «hola»}
[/symple_box]

Los bloques nos permiten hacer muy flexibles los métodos, por ejemplo alguien puede escribir el método filtrar que devuelve un subconjunto de elementos pero permitir que se reciba el código del como filtrar.

Los arrays de Ruby en programación tienen un método llamado .select que recibe un bloque donde se especifica el criterio a seleccionar.

[symple_box color=»black» fade_in=»false» float=»center» text_align=»left» width=»»]
a = [1,2,3,4,5,6]

a.select(){|x| x.even?}
[/symple_box]

  • Los paréntesis del select pueden ser omitidos.

Al método select aplica el bloque a cada uno de los elementos, en el ejemplo mostrado, x, es el iterador mientras que x.even? es lo que se aplicará a cada elemento. Si el bloque resulta en true entonces el elemento es seleccionado y de otra forma es rechazado.

Así como existe select, existen muchos otros similares e igualmente poderosos, como map, group_by e inject

map permite aplicar una función

[symple_box color=»black» fade_in=»false» float=»center» text_align=»left» width=»»]
a = [1, 2, 3, 4, 5, 6]

a.map(){ |x| x * 2 } # [2, 4, 6, 8, 10, 12]
[/symple_box]

group_by permite agrupar los elementos de un array por categoría

[symple_box color=»black» fade_in=»false» float=»center» text_align=»left» width=»»]
a = [1,2,3,4,5,6]

a.group_by(){|x| x.even? }
# {false=>[1, 3, 5], true=>[2, 4, 6]}

b = [«hola», «mundo», 2, 3, 4]

puts b.group_by(){ |x| x.class }
# {String=>[«hola», «mundo»], Integer=>[2, 3, 4]}
[/symple_box]

inject permite reducir un arreglo a un solo elemento

[symple_box color=»black» fade_in=»false» float=»center» text_align=»left» width=»»]
a = [1,2,3,4,5,6]

a.inject(0){|x, sum| sum + x} # 21
[/symple_box]

Reutilizando con procs

Además de los bloques existen los procs, estos permiten reutilizar bloques.

[symple_box color=»black» fade_in=»false» float=»center» text_align=»left» width=»»]
proc = Proc.new {puts «hola»}
proc.call #hola
proc.call #hola
proc.call #hola
[/symple_box]

Los procs son objetos que almacenan un bloque y podemos llamarlo con .call

Llamando a un método con un proc

Como ya vimos al momento de llamar un método podemos especificar un bloque, este bloque se llamara en el yield del método.

[symple_box color=»black» fade_in=»false» float=»center» text_align=»left» width=»»]
suma(2, 3){puts «hola»}
[/symple_box]

Podemos lograr exactamente lo mismo con un proc utilizando el operador &

[symple_box color=»black» fade_in=»false» float=»center» text_align=»left» width=»»]
mi_proc = Proc.new {puts «hola»}
suma(2, 3, &mi_proc)
[/symple_box]

Es importante saber que solo el último argumento puede tener el operador, porque un método no puede recibir dos bloques (de esta forma). De hecho si combinamos ambos métodos obtendremos un error:

[symple_box color=»black» fade_in=»false» float=»center» text_align=»left» width=»»]
metodo(&proc) {puts «hola»} # both block arg and actual block given
[/symple_box]

Puede ser confuso que hayan muchas formas de hacer lo mismo, pero no debemos perder de vista que el objetivo de los bloques es flexibilizar las funciones para hacerlas reutilizables y los procs nos permiten reutilizar bloques.

Lambdas

Los lambdas (a veces llamados funciones anónimas) son muy similares a los procs, tanto que se ocupan de la misma forma.

[symple_box color=»black» fade_in=»false» float=»center» text_align=»left» width=»»]
l = lambda {puts «hola»}
l.call() #hola
[/symple_box]

Y también se pueden pasar a un método utilizando el operador ampersand

[symple_box color=»black» fade_in=»false» float=»center» text_align=»left» width=»»]
def suma(a, b)
yield
end

l = lambda {puts «hola»}
puts suma(2, 3, &l)
# hola
[/symple_box]

Estudiaremos la diferencia entre procs y lambdas cuando aprendamos de parámetros:

Parámetros en procs y lambdas

La idea de la programación funcional es trabajar con funciones, y para eso necesitamos definir parámetros:

[symple_box color=»black» fade_in=»false» float=»center» text_align=»left» width=»»]
l = lambda { |x, y| x + y }
puts l.call(2,3)

p = Proc.new { |x, y| x + y }
puts p.call(2,3)
[/symple_box]

Una de las diferencias mas importantes entre proc y lambdas es que los lambda todos los parámetros son obligatorios, mientras que los procs no, veamos un ejemplo a partir de un error:

[symple_box color=»black» fade_in=»false» float=»center» text_align=»left» width=»»]
p = Proc.new { |x, y| x + y }
puts p.call(2)

#`+’: nil can’t be coerced into Integer (TypeError)
[/symple_box]

O sea el proc fue llamado pero falló al intentar sumar 2 con nil, miremos el caso de lambda

[symple_box color=»black» fade_in=»false» float=»center» text_align=»left» width=»»]
l = lambda { |x, y| x + y }
puts l.call(2)

#wrong number of arguments (given 1, expected 2) (ArgumentError)
[/symple_box]

Programación funcional

Los bloques, lambdas y procs son los elementos principales de la programación funcional en ruby, con ellos podemos escribir funciones de alto nivel para procesar datos.