4 votes

Règles de comptage des paramètres Ruby

Quelles sont les règles en Ruby concernant le nombre de paramètres pour les différents constructs de type fonction et comment ils sont appelés?

Par exemple, j'ai remarqué que lorsque des blocs avec plusieurs paramètres reçoivent un unique paramètre de type tableau, il est étendu, et cela ne semble pas s'appliquer aux méthodes. Je vois souvent cela avec les méthodes du module Enumerable sur un objet Hash.

{a: 5}.map{|x| x} # [[:a, 5]]
{a: 5}.map{|k, v| [k, v]} # [[:a, 5]]
[[:a, 5]].map{|x| x} # [[:a, 5]]
[[:a, 5]].map{|k, v| [k, v]} # [[:a, 5]] 

proc1 = Proc.new{|x| x}
proc1.call 5 # 5
proc1.call 5, 6 # 5
proc1.call [5, 6] # [5, 6]

proc2 = Proc.new{|k, v| [k, v]}
proc2.call 5 # [5, nil]
proc2.call 5, 6 # [5, 6]
proc2.call [5, 6] # [5, 6], pas [[5, 6], nil]

def f(k, v); [k, v] end
f 5 # ArgumentError
f 5, 6 # [5, 6]
f [5, 6] # ArgumentError

def g(*vargs); vargs end
g 5 # [5]
g 5, 6 # [5, 6]
g [5, 6] # [[5, 6]]

Cependant, la documentation pour Proc.call ne semble pas mentionner cela.

Ensuite, il y a aussi les lambda qui sont légèrement différents, les méthodes en tant que Proc utilisant &:nom, et peut-être quelques autres. Et je ne suis pas entièrement sûr que Proc.new{|x| x}.call soit exactement la même chose que le yield dans une méthode_qui_prend_un_bloc{|x| x}.

7voto

ThomasSevestre Points 1332

La raison derrière cela est l'attribution de variables multiples et l'auto-spart

Prenons votre exemple proc2 (avec un cas d'utilisation supplémentaire intéressant) :

proc2 = Proc.new{|k, v| [k, v]}
proc2.call 5 # [5, nil]
proc2.call 5, 6 # [5, 6]
proc2.call [5, 6] # [5, 6], pas [[5, 6], nil]
proc2.call [5, 6, 7] # [5, 6]

avec ruby vous pouvez faire une affectation de variables multiples :

k, v= 5 # => k=5, v=nil
k, v= 5, 6 # => k=5, v=6
k, v= 5, 6, 7 # => k=5, v=6, 7 n'est pas assigné

Vous pouvez également étendre un tableau avec l'opérateur splat :

k, v= *[5, 6] # => k=5, v=6

Vous pouvez également regrouper plusieurs variables dans un tableau avec l'opérateur splat :

k, *v= *[5, 6, 7] # => k=5, v=[6, 7]

ruby peut auto-splatter quand c'est approprié :

k, v= [5, 6] # => k=5, v=6
k, v= [5, 6, 7] # => k=5, v=6, 7 n'est pas assigné

autant que je sache, l'auto-splatter s'applique uniquement aux variables et à l'assignation de paramètres Proc

0voto

Jörg W Mittag Points 153275

Ceci est un reste de la façon dont les arguments de bloc fonctionnaient avant 1.9.

Avant Ruby 1.9, les blocs utilisaient essentiellement des sémantiques d'assignation pour la liaison des paramètres. Donc, si vous aviez un bloc comme

{|a, b, c|}

et quelque chose comme

yield foo, bar, baz

cela serait littéralement traité comme

a, b, c, = foo, bar, baz

Et quand je dis "littéralement", je le pense. Par exemple, ce qui suit serait une implémentation légale et entièrement fonctionnelle d'une méthode initialize:

define_method(:initialize) do |@foo, @bar| end

Maintenant, vous pourriez dire, attendez une minute, la méthode ne fait rien ! Elle est vide ! Oui, vous avez raison, la méthode ne fait rien, mais parce que les blocs utilisent des sémantiques d'assignation, quand je fais maintenant

Foo.new(23, 42)

cela sera littéralement interprété comme

@foo, @bar = 23, 42

Vous pourriez réellement faire quelque chose de encore plus fou que cela : vous pourriez réellement utiliser des méthodes setters en tant que paramètres de bloc !

{|foo.bar, baz.quux|}

Maintenant, quand je yield quelque chose à ce bloc, il invoquera effectivement foo.bar= et baz.quux=.

Cette "fonctionnalité" a été supprimée en Ruby 1.9+. Vous ne pouvez plus utiliser de variables d'instance, de variables globales, de variables de classe ou de méthodes setters en tant que paramètres de bloc. Mais, il y a une application utile de cette fonctionnalité que les concepteurs de Ruby ont voulu préserver : la déstructuration automatique des paires clé-valeur dans Hash#each.

Donc, même si de nos jours, les blocs n'utilisent pas d'assignation réelle pour la liaison des arguments, les sémantiques sont encore assez proches. Cela signifie, par exemple, que vous pouvez ignorer les arguments à la fin, en ayant moins de paramètres. (Cela signifie, en retour, lors de la conception d'une API, vous devriez ordonner les arguments de bloc par importance !) Ou que vous pouvez déstructurer un simple tableau.

A cet égard, les Procs se comportent comme des blocs, et les lambdas se comportent comme des méthodes. (La même chose est vraie pour la gestion de return, d'ailleurs : dans les blocs et les Procs, return retourne de la méthode englobante, dans les méthodes et les lambdas de la méthode et du lambda eux-mêmes.) Mnémonique utile : bloc rime avec Proc, et méthode et lambda sont tous les deux grecs.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X