Skip to content

uqbar-paco/tadp-ruby-age-of-empires-meta

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 

Repository files navigation

Clase 3 TADP 2C2016

METAPROGRAMACION

Es el proceso o la práctica por la cual escribimos programas que generan, manipulan o utilizan otros programas.

Ejemplos
  • Un compilador se puede pensar como un programa que genera otro programa.
  • Un formateador de código es un programa que manipula otro programa.
  • Una herramienta como javadoc utiliza nuestro programa para generar su documentación.
Para qué se usa la metaprogramación?

En general la metaprogramación se utiliza más fuertemente en el desarrollo de frameworks y herramientas.

Dado que los frameworks van a resolver cierta problemática de las aplicaciones, no van a estar diseñados para ninguna en particular. Es decir, la idea de framework es que se va a poder aplicar y utilizar en diferentes dominios desconocidos para el creador del framework.

Ejemplos:

  • ORM's (como hibernate): Que van a encargarse de persistir las instancias de nuestras clases sin siquiera conocerlas de antemano.
  • Frameworks de UI: Que deberán saber mostras / bindear cualquier objeto.
  • Frameworks de Testing (como JUnit): suelen usar metaprogramación para analizar la clase de Test y encontrar los tests que se deben correr.
  • Javadoc: Es una herramienta como el compilador de java, que lee el código fuente y genera documentación.
  • Code Coverage: Herramientas que miden cuánto de nuestro código es realmente ejecutado al correr los tests, y cuales lineas no.
  • Analizadores de código: Que evalúan nuestro código y genera métricas o miden violaciones a reglas definidas. Como el estilo de código, complejidad ciclomática, etc.

Reflection

Es un caso particular de metaprogramación, donde "metaprogramamos" en el mismo lenguaje en que están escritos (o vamos a escribir) los programas. Es decir, todo desde el mismo lenguaje.

Tipos de reflection

Para esto, generalmente, es necesario contar con facilidades o herramientas específicas, digamos "soporte" del lenguaje. Entonces reflection, además, abarca los siguientes items que vamos a mencionar en esta lista:

  • Introspection:Se refiere a la capacidad de un sistema, de analizarse a sí mismo. Algo así como la introspección humana, pero en términos de programa. Para eso, el lenguaje debe proveer ciertas herramientas, que le permitan al mismo programa, "ver" o "reflejar" cada uno de sus componentes.
  • Self-Modification:Es la capacidad de un programa de modificarse a sí mismo. Nuevamente esto requiere cierto soporte del lenguaje. Y las limitaciones van a depender de este soporte.
  • Intercession:Es la capacidad de modificar la semántica del modelo que estamos manipulando,desde el mismo lenguaje.

Modelos y metamodelos

Así como todo programa construye un modelo para describir su dominio, los lenguajes pueden hacer lo mismo para describir sus abstracciones. El domino de un metaprograma son los programas.

El programa describe las características de los elementos del dominio utilizando clases, métodos, atributos entre otros. Entonces, el modelo puede contener por ejemplo una clase Guerrero, que modela a los guerreros en el domino.

Un metaprograma usará el metamodelo que describe al programa base. Así como en el dominio hay guerreros, los elementos del "metadominio" (= programa base) serán los construcciones del lenguaje, por ejemplo, clases, atributos, métodos.

Practica

Vamos a usar el código de la clase anterior como ejemplo https://github.com/uqbar-paco/tadp-ruby-age-of-empires

Podemos comenzar con un poco de introspection y preguntarle a un objeto desde su clase hasta que metodos tiene.

atila = Guerrero.new
atila.class  #=> Guerrero
atila.class.superclass  #=> Object

atila.methods  #=> [:potencial_defensivo, :sufri_danio, :descansar, :energia, :energia=,...] 
Guerrero.instance_methods #=> Idem a atila.methods
Guerrero.instance_methods(false) #=> [:descansar_atacante, :descansar_defensor,...] (solo los de la clase guerrero)

Tambien podemos empezar a interactuar con los objetos de otra manera, como mandarle mensajes de otra manera.

atila.send(:potencial_ofensivo)  #=> 20
atila.send(:descansar)  #=> 110

# con send no existen los metodos privados, la seguridad es una sensacion
class A
    private
    def metodo_privado
        'cosa privada, no te metas'
    end    
end
objeto = A.new
objeto.metodo_privado #=> NoMethodError: private method `metodo_privado' called for #<A:direccion en memoria del objeto>
objeto.send(:metodo_privado)  #=> "cosa privada, no te metas"

Tambien podemos obtener un metodo e invocarlo

metodo = atila.method(:potencial_ofensivo)  #=> #<Method: Guerrero(Atacante)#potencial_ofensivo>
metodo.call  #=> 20

Algo interesante que podemos hacer es pedirle un metodo de instancia a una clase. A este metodo se lo llama UnBound method, ya que no esta asociado a ninguna instancia de esa clase. Podemos asociarlo a un objeto siempre y cuando este dentro de la jerarquia de clases.

class Padre
    def correr
        'correr como padre'
    end
end

class Hijo < Padre
    def correr
        'correr como hijo'
    end
end
metodo = Padre.instance_method(:correr)  #=>#<UnboundMethod: Padre#correr>
metodo.bind(Hijo.new).call  #=> 'correr como padre'

Como vemos los Unbound methods se escapan al metodo lookup. Tambien podemos pregunarle a los metodos cosas.

metodo = atila.method(:atacar) #=> #<Method: Guerrero(Atacante)#atacar>
metodo.arity  #=> 1
metodo.parameters #=> [[:req, :un_defensor]]
metodo.owner  #=> Atacante (donde esta definido)

Asi como ya estuvimos jugando con las clases, objetos y metodos, tambien podemos jugar con las variables.

atila.instance_variables #=> [:@potencial_ofensivo, :@energia, :@potencial_defensivo]
atila.instance_variable_get(:@energia)  #=> 100
atila.instance_variable_set(:@energia, 50) #=> 50
atila.instance_variable_get(:@energia)  #=> 50

Self-Modification

Open classes

Nos permite definir métodos y atributos en una clase ya existente. Es una forma de self modification con azucar sintáctica para no tener que hacerlo mediante mensajes.

class String
  def importante
    self + '!'
  end
end
'aprobe'.importante  #=> "aprobe!"

#Cambiar métodos
class Fixnum
  def +(x)
    123
  end
end
2+2  #=> 123

Otra manera de abrir las clases y definir metodos es usando en la clase el define_method pero este es privado y como ya vimos podemos pasarlo por arriba invocando el send.

 
Guerrero.send(:define_method, :saluda) {
  'Hola'
}
Guerrero.new.saluda  #=> "Hola"

Aca podemos hacer referencia a dos practicas de programacion Duck Typing y Monkey patching

Duck Typing

Debido a que ruby es un lenguaje dinamicamente tipado, hacemos referencia a un tipo de dato por el comportamiento que tiene.

...if it walks like a duck and talks like a duck, it’s a duck, right?

Si tenemos un objeto que cuando hace ruido hace "cuak" y camina como un pato, probablemente lo sea, y deberia poder continuar usando este objeto como si fuera uno.

Monkey Patching

...if it walks like a duck and talks like a duck, it’s a duck, right? So if this duck is not giving you the noise that you want, you’ve got to just punch that duck until it returns what you expect.

Hace referencia a la posibilidad de practicamente modificar un tipo a gusto y piacere para que responda a nuestras necesidades y realizar otro tipo de operaciones como si fuera otro.

Tambien podemos empezar a hacer algunas cosas mas locas, como agregarle comportamiento a un unico objeto

atila.define_singleton_method(:saluda) {
  'Hola soy Atila'
}
atila.saluda  #=> "Hola soy Atila"
Guerrero.new.saludo # NoMethodError

METAMODELO

Empezamos a dibujar el modelo de clases. Vamos a jugar un poco más con el metamodelo, ya sabemos que existe el mensaje class que lo entienden todos los objetos, si queremos saber la superclase de una clase tenemos el mensaje superclass. Podríamos pensar en base a eso quiénes le proveen comportamiento a cada uno de nuestros objetos.

zorro = Espadachin.new(Espada.new(123))

Tenemos al zorro que es instancia de Espadachin, que hereda de Guerrero. Si le pedimos los métodos al zorro vemos que incluye a los instance_methods de Espadachin y de Guerrero, así como todos los que se definen para las instancias de Object.

Autoclases-EigenClass

Pero nosotros no le agregamos comportamiento sólo a las instancias, también teníamos un par de métodos de clase que habíamos definido para Peloton, como por ejemplo cobarde.

Peloton.cobarde([])

Quién provee ese comportamiento? La clase de Peloton es Class, y este comportamiento no se agregó para todas las clases así que no puede estar definido dentro de Class.

Peloton.methods.include? :cobarde  #=> true
Peloton.class.instance_methods.include? :new  #=> true
Peloton.class.instance_methods.include? :cobarde  #=> false

Esto sólo lo entiende la clase Peloton, o sea que está definido para un sólo objeto. El objeto que le provee el comportamiento a un sólo objeto es la autoclase. En Ruby podemos obtener la autoclase de un objeto mandándole singleton_class.

Peloton.singleton_class.instance_methods(false)  #=> [:cobarde]

Todos los objetos tienen una singleton class, con lo cual podemos definirle comportamiento a atila y que sea el único guerrero con ese comportamiento. Incluir un mixin a una instancia(con include en la singleton class o extend en la intancia):

module W
  def m
    123
  end
end
a = Guerrero.new
a.singleton_class.include W
a.m  #=> 123
b = Guerrero.new
b.extend W
b.m  #=>123

Agregamos el test en el que atila cuando se lastima descansa y se come un pollo incorporando esta línea para que entienda comerse_un_pollo

atila = Guerrero.new
atila.singleton_class.send(:define_method, :comerse_un_pollo, proc { @energia += 20 })
atila.energia
atila.comerse_un_pollo
atila.energia
Guerrero.new.comerse_un_pollo  # NoMethodError

También vemos que se puede tener properties para un objeto solo

atila.singleton_class.send(:attr_accessor, :edad)
atila.edad = 5
atila.edad  #=> 5
Guerrero.new.edad  #=> NoMethodError

Vemos que no aparecen los mixins que tiene la clase Guerrero si vamos preguntando las super clases. El mensaje superclass no mustra los mixins (porque no son clases), para poder ver la linearización tenemos que pedir quienes proveen comportamiento con el mensaje ancestors.

Guerrero.ancestors  #=> [Guerrero, Defensor, Observable, Atacante, Object, Kernel, BasicObject]

Nota: Las ultimas veriones de ruby incluyen a la singleton class de Guerrero, las anteriores a 2.1.0 NO incluyen a las singleton classes:

Guerrero.new.singleton_class.ancestors
[#<Class:#<Guerrero:0x00000001d6c8c8>>,
Guerrero,
Defensor,
Atacante,
Object,
PP::ObjectMixin,
Kernel,
BasicObject]

Dibujar los ancestors de Guerrero en el pizarron. Dibujar la singleton class de atila. Agregamos un método de clase a Guerrero:

class Guerrero
  def self.gritar
    “haaaa”
  end
end
atila.gritar  #=> NoMethodError
Guerrero.gritar  #=> haaaa

Dibujar la singleton class de Guerrero (donde definí “gritar”)

Espadachin.gritar  #=>haaaa

About

Clase 3 metaprogramacion 2016

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •