web-dev-qa-db-fra.com

ruby module_function vs module inclus

Dans Ruby, je comprends que les fonctions du module peuvent être mises à disposition sans mélange dans le module en utilisant module_function comme indiqué ici. Je peux voir comment cela est utile pour que vous puissiez utiliser la fonction sans mélanger dans le module.

module MyModule
  def do_something
    puts "hello world"
  end
  module_function :do_something
end

Ma question est cependant de savoir pourquoi vous voudrez peut-être que la fonction soit définie de ces deux manières.

Pourquoi ne pas simplement

def MyModule.do_something

OR

def do_something

Dans quel type de cas serait-il utile d'avoir la fonction disponible pour être mélangée ou utilisée comme méthode statique?

47
Jeff Storey

Pensez à énumérable .

Ceci est l'exemple parfait du moment où vous devez l'inclure dans un module. Si votre classe définit #each, vous obtenez beaucoup de bonté simplement en incluant un module (#map, #select, etc.). C'est le seul cas où j'utilise des modules en tant que mixins - lorsque le module fournit des fonctionnalités en termes de quelques méthodes, définies dans la classe dans laquelle vous l'incluez. Je peux affirmer que ce devrait être le seul cas en général.

Quant à la définition de méthodes "statiques", une meilleure approche serait:

module MyModule
  def self.do_something
  end
end

Vous n'avez pas vraiment besoin d'appeler #module_function. Je pense que c'est juste des trucs hérités bizarres.

Vous pouvez même faire ceci:

module MyModule
  extend self

  def do_something
  end
end

... mais cela ne fonctionnera pas bien si vous souhaitez également inclure le module quelque part. Je suggère de l'éviter jusqu'à ce que vous appreniez les subtilités de la méta-programmation Ruby.

Enfin, si vous faites juste:

def do_something
end

... cela ne se terminera pas comme une fonction globale, mais comme une méthode privée sur Object (il n'y a pas de fonctions dans Ruby, juste des méthodes). Il y a deux inconvénients. Tout d'abord, vous n'avez pas d'espaces de noms - si vous définissez une autre fonction avec le même nom, c'est celle qui sera évaluée plus tard que vous obtenez. Deuxièmement, si vous avez mis en œuvre des fonctionnalités en termes de #method_missing, avoir une méthode privée dans Object l'occultera. Et enfin, le patch de singe Object est juste une mauvaise affaire :)

ÉDITER:

module_function peut être utilisé d'une manière similaire à private:

module Something
  def foo
    puts 'foo'
  end

  module_function

  def bar
    puts 'bar'
  end
end

De cette façon, vous pouvez appeler Something.bar, mais pas Something.foo. Si vous définissez d'autres méthodes après cet appel à module_function, ils seraient également disponibles sans se mélanger.

Je n'aime pas ça pour deux raisons, cependant. Premièrement, les modules qui sont à la fois mixés et qui ont des méthodes "statiques" semblent un peu douteux. Il peut y avoir des cas valides, mais ce ne sera pas si fréquent. Comme je l'ai dit, je préfère utiliser un module comme espace de noms ou le mélanger, mais pas les deux.

Deuxièmement, dans cet exemple, bar serait également disponible pour les classes/modules qui se mélangent dans Something. Je ne sais pas quand cela est souhaitable, car soit la méthode utilise self et elle doit être mélangée, soit pas et alors elle n'a pas besoin d'être mélangée.

Je pense qu'en utilisant module_function sans passer le nom de la méthode est utilisé plus souvent qu'avec. Il en va de même pour private et protected.

41
Stefan Kanev

C'est un bon moyen pour une bibliothèque Ruby d'offrir des fonctionnalités qui n'utilisent pas (beaucoup) d'état interne. Donc si vous (par exemple) voulez offrir une fonction sin et ne pas ' Si vous voulez polluer l'espace de noms "global" (Object), vous pouvez le définir comme méthode de classe sous une constante (Math).

Cependant, un développeur d'applications qui souhaite écrire une application mathématique peut avoir besoin de sin toutes les deux lignes. Si la méthode est également une méthode d'instance, elle peut simplement inclure le Math (ou My::Awesome::Nested::Library) et peut désormais appeler directement sin (exemple stdlib).

Il s'agit vraiment de rendre une bibliothèque plus confortable pour ses utilisateurs. Ils peuvent se choisir eux-mêmes, s'ils souhaitent que les fonctionnalités de votre bibliothèque soient de niveau supérieur.

Soit dit en passant, vous pouvez obtenir une fonctionnalité similaire comme module_function en utilisant: extend self (dans la première ligne du module). À mon avis, cela semble mieux et rend les choses un peu plus claires à comprendre.

Mise à jour: Plus d'informations de fond dans cet article de blog .

14
J-_-L

Si vous souhaitez consulter un exemple de travail, consultez la gemme chronique:

https://github.com/mojombo/chronic/blob/master/lib/chronic/handlers.rb

et Handlers est inclus dans la classe Parser ici:

https://github.com/mojombo/chronic/blob/master/lib/chronic/parser.rb

Il utilise module_function pour envoyer les méthodes des gestionnaires à des instances spécifiques de gestionnaire en utilisant la méthode d'invocation de cette instance.

2
TalkativeTree