arrow_back Volver
Inicio keyboard_arrow_right Artículos keyboard_arrow_right Artículo

Qué es la metaprogramacion

Uriel Hernández

CTO de Código Facilito

av_timer 6 Min. de lectura

remove_red_eye 19515 visitas

calendar_today 01 Mayo 2018

La metaprogramación o metaprogramming como decimos en inglés, es uno de los términos más desafiantes que un desarrollador puede encontrar, en resumen, solemos pensar en metaprogramación como la habilidad de usar código para generar código, el problema es que ir de, con metaprogramación puedo generar código a efectivamente aprovechar la implementación de metaprogramación de un lenguaje, hay un largo camino.

Para qué usamos metaprogramación

Imagen cover sobre el artículo de metaprogramación

Primero tratemos de entender por qué el concepto es importante, a final de cuentas, es probable que hasta ahora hayas sobrevivido sin una rutina de código que genere más código, a final de cuentas, ese código también lo podemos escribir nosotros, ¿cierto?

id title durationl
1 Curso de Ruby 1500
2 Curso de Laravel 200
3 Curso de JavaScript 900

Considera que tenemos una tabla Cursos, que tiene un título y una duración, en versiones anteriores de Rails, era posible usar un método como el siguiente para buscar elementos en la tabla:

def search_courses
    Course.find_by_title_and_duration(‘Ruby’,100)
end

La pregunta es, de dónde salen dichos métodos, es imposible que los desarrolladores del framework pudieran definir un método para cada combinación de campos posible en un programa. La respuesta es metaprogramación.

Ahora, considera el siguiente programa de Ruby:

class HTML
 def self.method_missing(nomb_method, *args, &block)
   tag(nomb_method,*args,&block)
 end
 def self.tag(tag_name,*args,&block)
    "<#{tag_name}>#{args.last} #{yield if block_given?}</#{tag_name}>"
 end
end

El mismo se utiliza de la siguiente manera:

html = HTML.div do
 HTML.header do
   HTML.h1 "Titulo de la pagina"
 end +
 HTML.article do
   HTML.p "Hola a todos"
 end
end

puts html

El resultado de este programa es el siguiente:

<div>
   <header>
      <h1>Titulo de la pagina </h1>
   </header>
   <article>
      <p>Hola a todos </p>
   </article>
</div>

Nota como cada etiqueta es un método, ¿tuve que definirlos todos? No, usé metaprogramación.

Puedes ejecutar el programa por tu cuenta en la siguiente consola:

Bien usada, la metaprogramación nos permite crear aplicaciones dinámicas y expresivas, mal usado, la metaprogramación puede transformarse en una caja negra que entrega resultados erróneos y nadie entiende por qué.

Usando metaprogramación

Algo muy importante que necesitas saber es que cada lenguaje tiene implementaciones distintas de metaprogramación, y que la línea que separa la metaprogramación del código dinámico, es muy difusa, lo que algunos consideran metaprogramación, otros no. Así que, ten eso en cuenta.

Ejecutando strings como código

Una de las formas más simples de metaprogramación es una función que reciba una cadena y la evalúe como código, en JavaScript tenemos la función eval.

eval(‘console.log(2+2)’)); // Imprime 4 en consola.

Tal como JavaScript, muchos lenguajes tienen una implementación de la función eval. Considerando que con código podemos construir una cadena, eval se convierte en la forma más sencilla de metaprogramación, generamos código, lo almacenamos en una cadena y lo ejecutamos. Aunque es metaprogramación, no seré yo el primero en decirte que esta clase de función debe tratarse con cuidado, generalmente son propensas a dejar brechas de seguridad o el rendimiento del código ejecutado es más lento.

Introspección y Reflexión

Otras formas de metaprogramación son la introspección y la reflexión, conceptos relacionados entre sí que representan formas más elegantes de metaprogramación.

La introspección es la cualidad de un lenguaje de responder a la pregunta ¿Qué soy? Cuando un lenguaje, a través de APIs y métodos puede entregarte información acerca de lo que está sucediendo, a eso se le llama introspección. La introspección como tal no es metaprogramación, a final de cuentas, sólo se nos está entregando información.

La introspección se convierte en metaprogramación cuando basados en la información entregada, tomamos decisiones que afectan la ejecución del programa. Casi todos los lenguajes tienen formas de evaluar el estado actual del código, en Ruby por ejemplo podemos usar el método methods que nos entrega un arreglo con todos los métodos del objeto.

# Devuelve todos los métodos que tienen los enteros (números)
2.methods

JavaScript tiene en el objeto Object varios métodos que caben en la categoría de introspección tales como getOwnPropertyNames, keys, isFrozen y muchos más, de hecho expresiones más sencillas como typeof que nos devuelve el tipo del objeto pueden considerarse introspección.

Object.getOwnPropertyNames({}) // Retorna las propiedades del objeto

typeof "hola" // Devuelve el tipo del dato, en este caso string

Reflección por otro lado, es la habilidad de alterar la información de los elementos del lenguaje, a diferencia de la introspección que solo nos entrega información, con reflección alteramos está información.

Separar las modificaciones que se consideran reflección y las que no, es muy simple, la información que se modifica con reflección es la metadata, es decir, datos descriptivos de los datos, lo que quiere decir es que si yo modifico un objeto que almacena un número, estoy modificando el dato:

//Esto no es reflexión
let numero = 20;
numero = 10;

Por otro lado, si modifico el contenedor de dicho dato, estoy modificando los datos de los datos.Parece un juego de palabras, ¿no? Un ejemplo más simple es el de la fotografía, los datos de una fotografía son los pixeles que la construyen, mientras que los metadatos son detalles que describen la imagen, como por ejemplo la ubicación en que fue tomada. Así pasa con los programas.

Los ejemplos de reflección con más complejos, considera Ruby por ejemplo. El comportamiento base del lenguaje nos dice que la siguiente expresión nos dará 4

#Devuelve 4, una simple suma
2 + 2

Pero qué tal si no. En Ruby podemos modificar el comportamiento de operaciones como esta, el código se vería así:

class Integer
  def +(value)
    self * value
  end
end

puts 2 +3

¿Qué crees que imprimirá el código anterior? Compruébalo tu mismo en la siguiente consola interactiva:

Aunque no lo creas, imprime 6, ya que modificamos el operador suma para que en lugar de sumara, multiplicara. Esto es reflexión porque modificamos el contenedor, la clase Integer.

Por supuesto que no tiene caso modificar el comportamiento de los enteros, pero el mensaje es el mismo, modificamos el comportamiento para que se comporte de otra manera, a eso llamamos reflexión.

Un ejemplo más práctico es crear un clase que pueda ser iterada (recorrida por un ciclo) de manera que tu controles cómo se va a recorrer tu estructura cuando la coloques en el ciclo. A continuación un ejemplo en JavaScript

class Document{
 constructor(content){
  this.content = content;
 }

 [Symbol.iterator](){
   let index = 0;
   let words = this.content.split(" ");
    return{
      next: ()=>{
        let currentValue = words[index++];
        return {value:currentValue, done:index > words.length};
      }
    }
  }
}

Esta clase es iterable y se puede colocar en un ciclo como ves a continuación:

let doc = new Document("Hola mundo como estas");

for(let word of doc) {
  console.log(word);
}

Hay muchas cosas avanzadas aquí, lo importante es que percibas como hicimos que nuestro objeto Documento funcionara dentro de un ciclo for of, dándole comportamiento personalizado a la iteración de un objeto, en este caso, que recorriera con el ciclo palabra por palabra nuestra estructura Documento.

Puedes jugar con este ejemplo en la siguiente consola:

Parte de cómo un lenguaje nos permite implementar Metaprogramación es exponiendo las partes internas del lenguaje para que podamos manipularlas y controlarlas, ahí es donde la retrospección entra en juego.

Conclusión

Con esto, cubrimos 3 formas distintas de metaprogramación, la ejecución de código vía un string, que como discutimos es la más simple, pero no la más eficiente, profesional o usada.

La introspección y la reflección, que a través de la exposición de meta datos del lenguaje y su modificación, nos permiten alterar el cómo se ejecuta un programa, ofreciéndonos mayor control para hacer código más legible y dinámico.

Estos no son los únicos conceptos, también tenemos el de MonkeyPatching, el de Templates en C++ y muchos más, recuerda que cada lenguaje decide implementar esta metodología de una manera distinta.

¿Interesante, no? Como puedes ver, hay lenguajes que son más amigables para introducirse en este concepto, mis dos recomendaciones principales son Ruby y Python, son lenguajes diseñados con el concepto de metaprogramación en mente, por lo que la sintaxis puede ser más clara y familiar. Si no has usado antes estos lenguajes ,te recomiendo seguir nuestros cursos:

Curso de Python: https://codigofacilito.com/cursos/python-profesional Curso de Ruby: https://codigofacilito.com/cursos/ruby-2

Lo más importante es que sepas que como en todo, la práctica hace al maestro y como dijo Jake en Hora de Aventura, el primer paso para ser bueno en algo, es ser muy malo en algo.

_ No olvides dejar tus comentarios en la sección de abajo, estos nos motivan a seguir escribiendo, además de que siempre puedes enriquecer la información de los artículos con tu propio conocimiento, haciendo aportes adicionales_