Vous entendez souvent que la métaprogrammation est quelque chose que seuls les ninjas Ruby utilisent, et que ce n'est tout simplement pas pour les mortels. Mais la vérité est que la métaprogrammation n’a rien d’effrayant. Cet article de blog servira à remettre en question ce type de réflexion et à rapprocher la métaprogrammation du développeur Ruby moyen afin qu'il puisse également profiter de ses avantages.
travailler avec des dates en javascriptMétaprogrammation Ruby: code d'écriture de code Tweet
Il convient de noter que la métaprogrammation peut signifier beaucoup et qu'elle peut souvent être très mal utilisée et aller à l'extrême en matière d'utilisation.Je vais donc essayer de donner des exemples du monde réel que tout le monde pourrait utiliser dans la programmation quotidienne.
Métaprogrammation est une technique par laquelle vous pouvez écrire du code qui écrit code par lui-même dynamiquement au moment de l'exécution. Cela signifie que vous pouvez définir des méthodes et des classes pendant l'exécution. Fou, non? En un mot, en utilisant la métaprogrammation, vous pouvez rouvrir et modifier des classes, attraper des méthodes qui n'existent pas et les créer à la volée, créer du code qui est SEC en évitant les répétitions, et plus encore.
Avant de plonger dans une métaprogrammation sérieuse, nous devons explorer les bases. Et la meilleure façon de le faire est par l'exemple. Commençons par un et comprenons la métaprogrammation Ruby étape par étape. Vous pouvez probablement deviner ce que fait ce code:
class Developer def self.backend 'I am backend developer' end def frontend 'I am frontend developer' end end
Nous avons défini une classe avec deux méthodes. La première méthode de cette classe est une méthode de classe et la seconde est une méthode d'instance. Ce sont des choses basiques dans Ruby, mais il se passe beaucoup plus derrière ce code que nous devons comprendre avant de continuer. Il est à noter que la classe Developer
lui-même est en fait un objet. Dans Ruby, tout est un objet, y compris les classes. Depuis Developer
est une instance, c'est une instance de la classe Class
. Voici à quoi ressemble le modèle objet Ruby:
p Developer.class # Class p Class.superclass # Module p Module.superclass # Object p Object.superclass # BasicObject
Une chose importante à comprendre ici est la signification de self
. Le frontend
method est une méthode régulière disponible sur les instances de la classe Developer
, mais pourquoi backend
méthode une méthode de classe? Chaque morceau de code exécuté dans Ruby est exécuté contre un soi . Lorsque l'interpréteur Ruby exécute un code, il garde toujours la trace de la valeur self
pour une ligne donnée. self
fait toujours référence à un objet, mais cet objet peut changer en fonction du code exécuté. Par exemple, à l'intérieur d'une définition de classe, le self
fait référence à la classe elle-même qui est une instance de la classe Class
.
class Developer p self end # Developer
À l'intérieur des méthodes d'instance, self
fait référence à une instance de la classe.
class Developer def frontend self end end p Developer.new.frontend # #
À l'intérieur des méthodes de classe, self
fait référence à la classe elle-même d'une manière (qui sera discutée plus en détail plus loin dans cet article):
class Developer def self.backend self end end p Developer.backend # Developer
C'est bien, mais qu'est-ce qu'une méthode de classe après tout? Avant de répondre à cette question, nous devons mentionner l'existence de quelque chose appelé métaclasse, également connu sous le nom de classe singleton et classe propre. Méthode de classe frontend
que nous avons défini précédemment n'est rien d'autre qu'une méthode d'instance définie dans la métaclasse pour l'objet Developer
! Une métaclasse est essentiellement une classe que Ruby crée et insère dans la hiérarchie d'héritage pour contenir les méthodes de classe, n'interférant donc pas avec les instances créées à partir de la classe.
Chaque objet de Ruby a le sien métaclasse . Il est en quelque sorte invisible pour un développeur, mais il est là et vous pouvez l'utiliser très facilement. Depuis notre classe Developer
est essentiellement un objet, il a sa propre métaclasse. À titre d’exemple, créons un objet d’une classe String
et manipulez sa métaclasse:
example = 'I'm a string object' def example.something self.upcase end p example.something # I'M A STRING OBJECT
Ce que nous avons fait ici, c'est que nous avons ajouté une méthode singleton something
à un objet. La différence entre les méthodes de classe et les méthodes singleton est que les méthodes de classe sont disponibles pour toutes les instances d'un objet de classe tandis que les méthodes singleton ne sont disponibles que pour cette seule instance. Les méthodes de classe sont largement utilisées alors que les méthodes singleton ne le sont pas tellement, mais les deux types de méthodes sont ajoutés à une métaclasse de cet objet.
L'exemple précédent pourrait être réécrit comme ceci:
example = 'I'm a string object' class << example def something self.upcase end end
La syntaxe est différente mais elle fait effectivement la même chose. Revenons maintenant à l'exemple précédent où nous avons créé Developer
class et explorez d'autres syntaxes pour définir une méthode de classe:
class Developer def self.backend 'I am backend developer' end end
C'est une définition de base que presque tout le monde utilise.
def Developer.backend 'I am backend developer' end
C'est la même chose, nous définissons le backend
méthode de classe pour Developer
. Nous n'avons pas utilisé self
mais définir une méthode comme celle-ci en fait effectivement une méthode de classe.
class Developer class << self def backend 'I am backend developer' end end end
Encore une fois, nous définissons une méthode de classe, mais en utilisant une syntaxe similaire à celle que nous avons utilisée pour définir une méthode singleton pour un String
objet. Vous remarquerez peut-être que nous avons utilisé self
ici qui fait référence à un Developer
objet lui-même. Nous avons d'abord ouvert Developer
classe, faisant de soi égal à Developer
classe. Ensuite, nous faisons class << self
, rendant self égal à la métaclasse de Developer
. Ensuite, nous définissons une méthode backend
sur la métaclasse de Developer
.
class << Developer def backend 'I am backend developer' end end
En définissant un bloc comme celui-ci, nous définissons self
à la métaclasse de Developer
pour la durée du bloc. En conséquence, le backend
est ajoutée à la métaclasse de Developer
, plutôt qu'à la classe elle-même.
Voyons comment cette métaclasse se comporte dans l'arborescence d'héritage:
Comme vous l'avez vu dans les exemples précédents, il n'y a aucune preuve réelle que la métaclasse existe même. Mais nous pouvons utiliser un petit hack qui peut nous montrer l'existence de cette classe invisible:
class Object def metaclass_example class << self self end end end
Si nous définissons une méthode d'instance dans Object
classe (oui, nous pouvons rouvrir n'importe quelle classe à tout moment, c'est encore une autre beauté de la métaprogrammation), nous aurons un self
se référant au Object
objet à l'intérieur. On peut alors utiliser class << self
syntaxe pour changer le courant soi pour pointer vers la métaclasse de l'objet courant. Puisque l'objet courant est Object
classe elle-même, ce serait la métaclasse de l'instance. La méthode renvoie self
qui est à ce stade une métaclasse elle-même. Ainsi, en appelant cette méthode d'instance sur n'importe quel objet, nous pouvons obtenir une métaclasse de cet objet. Définissons notre Developer
classe à nouveau et commencez à explorer un peu:
class Developer def frontend p 'inside instance method, self is: ' + self.to_s end class << self def backend p 'inside class method, self is: ' + self.to_s end end end developer = Developer.new developer.frontend # 'inside instance method, self is: #' Developer.backend # 'inside class method, self is: Developer' p 'inside metaclass, self is: ' + developer.metaclass_example.to_s # 'inside metaclass, self is: #'
Et pour le crescendo, voyons la preuve que frontend
est une méthode d'instance d'une classe et backend
est une méthode d'instance d'une métaclasse:
quelle langue utilise windows
p developer.class.instance_methods false # [:frontend] p developer.class.metaclass_example.instance_methods false # [:backend]
Cependant, pour obtenir la métaclasse, vous n’avez pas besoin de rouvrir Object
et ajoutez ce hack. Vous pouvez utiliser singleton_class
que Ruby fournit. C'est la même chose que metaclass_example
nous avons ajouté mais avec ce hack, vous pouvez réellement voir comment Ruby fonctionne sous le capot:
p developer.class.singleton_class.instance_methods false # [:backend]
Il existe une autre façon de créer une méthode de classe, à savoir en utilisant instance_eval
:
class Developer end Developer.instance_eval do p 'instance_eval - self is: ' + self.to_s def backend p 'inside a method self is: ' + self.to_s end end # 'instance_eval - self is: Developer' Developer.backend # 'inside a method self is: Developer'
Cet interpréteur de code Ruby évalue dans le contexte d'une instance, qui est dans ce cas un Developer
objet. Et lorsque vous définissez une méthode sur un objet, vous créez une méthode de classe ou une méthode singleton. Dans ce cas, il s'agit d'une méthode de classe - pour être exact, les méthodes de classe sont des méthodes singleton mais des méthodes singleton d'une classe, tandis que les autres sont des méthodes singleton d'un objet.
D'autre part, class_eval
évalue le code dans le contexte d'une classe au lieu d'une instance. Il rouvre pratiquement la classe. Voici comment class_eval
peut être utilisé pour créer une méthode d'instance:
Developer.class_eval do p 'class_eval - self is: ' + self.to_s def frontend p 'inside a method self is: ' + self.to_s end end # 'class_eval - self is: Developer' p developer = Developer.new # # developer.frontend # 'inside a method self is: #'
Pour résumer, lorsque vous appelez class_eval
méthode, vous changez self
pour faire référence à la classe d'origine et lorsque vous appelez instance_eval
, self
modifications pour faire référence à la métaclasse de la classe d'origine.
Un autre morceau de puzzle de métaprogrammation est method_missing
. Lorsque vous appelez une méthode sur un objet, Ruby entre d'abord dans la classe et parcourt ses méthodes d'instance. S'il n'y trouve pas la méthode, il poursuit sa recherche dans la chaîne des ancêtres. Si Ruby ne trouve toujours pas la méthode, il appelle une autre méthode nommée method_missing
qui est une méthode d'instance de Kernel
que chaque objet hérite. Puisque nous sommes sûrs que Ruby va éventuellement appeler cette méthode pour les méthodes manquantes, nous pouvons l'utiliser pour implémenter quelques astuces.
define_method
est une méthode définie dans Module
classe que vous pouvez utiliser pour créer des méthodes de manière dynamique. Pour utiliser define_method
, vous l'appelez avec le nom de la nouvelle méthode et un bloc où les paramètres du bloc deviennent les paramètres de la nouvelle méthode. Quelle est la différence entre l'utilisation de def
pour créer une méthode et define_method
? Il n'y a pas beaucoup de différence sauf que vous pouvez utiliser define_method
en combinaison avec method_missing
pour écrire le code DRY. Pour être exact, vous pouvez utiliser define_method
au lieu de def
pour manipuler les étendues lors de la définition d'une classe, mais c'est une toute autre histoire. Prenons un exemple simple:
class Developer define_method :frontend do |*my_arg| my_arg.inject(1, :*) end class < 100 p Developer.backend # undefined method 'backend' for Developer:Class (NoMethodError) Developer.create_backend p Developer.backend # 'Born from the ashes!'
Ceci montre comment define_method
a été utilisé pour créer une méthode d'instance sans utiliser de def
. Cependant, nous pouvons en faire beaucoup plus. Jetons un coup d'œil à cet extrait de code:
class Developer def coding_frontend p 'writing frontend' end def coding_backend p 'writing backend' end end developer = Developer.new developer.coding_frontend # 'writing frontend' developer.coding_backend # 'writing backend'
Ce code n'est pas DRY, mais utilise define_method
nous pouvons le faire SEC:
class Developer ['frontend', 'backend'].each do |method| define_method 'coding_#{method}' do p 'writing ' + method.to_s end end end developer = Developer.new developer.coding_frontend # 'writing frontend' developer.coding_backend # 'writing backend'
C’est beaucoup mieux, mais toujours pas parfait. Pourquoi? Si nous voulons ajouter une nouvelle méthode coding_debug
par exemple, nous devons mettre ceci 'debug'
dans le tableau. Mais en utilisant method_missing
nous pouvons résoudre ce problème:
class Developer def method_missing method, *args, &block return super method, *args, &block unless method.to_s =~ /^coding_w+/ self.class.send(:define_method, method) do p 'writing ' + method.to_s.gsub(/^coding_/, '').to_s end self.send method, *args, &block end end developer = Developer.new developer.coding_frontend developer.coding_backend developer.coding_debug
Ce morceau de code est un peu compliqué, alors décomposons-le. L'appel d'une méthode qui n'existe pas déclenchera method_missing
. Ici, nous voulons créer une nouvelle méthode uniquement lorsque le nom de la méthode commence par 'coding_'
. Sinon, nous appelons simplement super pour faire le travail de rapport d'une méthode qui manque réellement. Et nous utilisons simplement define_method
pour créer cette nouvelle méthode. C'est tout! Avec ce morceau de code, nous pouvons créer littéralement des milliers de nouvelles méthodes commençant par 'coding_'
, et c'est ce qui rend notre code DRY. Depuis define_method
se trouve être privé de Module
, nous devons utiliser send
pour l'invoquer.
numéros de carte de crédit de travail avec de l'argent 2017
Ce n'est que la pointe de l'iceberg. Devenir un Ruby Jedi , c'est le point de départ. Une fois que vous maîtrisez ces éléments de base de la métaprogrammation et que vous en comprenez vraiment l'essence, vous pouvez passer à quelque chose de plus complexe, par exemple créer le vôtre Langue spécifique au domaine (DSL). DSL est un sujet en soi, mais ces concepts de base sont une condition préalable à la compréhension des sujets avancés. Certaines des pierres précieuses les plus utilisées Rails ont été construits de cette façon et vous avez probablement utilisé son DSL sans même le savoir, comme RSpec et ActiveRecord.
J'espère que cet article peut vous aider à comprendre la métaprogrammation et peut-être même à créer votre propre DSL, que vous pouvez utiliser pour coder plus efficacement.