Que signifie || = (ou-égal) en Ruby?

340

Que signifie le code suivant dans Ruby?

||=

A-t-il un sens ou une raison à la syntaxe?

collimarco
la source

Réponses:

175

Cette question a été discutée si souvent sur les listes de diffusion Ruby et les blogs Ruby qu'il y a maintenant même des threads sur la liste de diffusion Ruby dont le seul but est de collecter des liens vers tous les autres threads de la liste de diffusion Ruby qui traitent de ce problème. .

En voici une: la liste définitive des fils et des pages || = (OR Equal)

Si vous voulez vraiment savoir ce qui se passe, jetez un œil à la section 11.4.2.3 «Affectations abrégées» du projet de spécification du langage Ruby .

En première approximation,

a ||= b

est équivalent à

a || a = b

et non équivalent à

a = a || b

Cependant, ce n'est qu'une première approximation, surtout si elle an'est pas définie. La sémantique diffère également selon qu'il s'agit d'une affectation de variable simple, d'une affectation de méthode ou d'une affectation d'indexation:

a    ||= b
a.c  ||= b
a[c] ||= b

sont tous traités différemment.

Jörg W Mittag
la source
2
Le deuxième lien a souffert de la pourriture des bits (commentaire de la méta par stackoverflow.com/users/540162/nightfirecat ).
Andrew Grimm
331
C'est une non-réponse très cryptique. La réponse courte semble être: a || = b signifie que si a n'est pas défini, affectez-lui la valeur de b, sinon laissez-le tranquille. (Ok, il y a des nuances et des cas spéciaux, mais c'est le cas de base.)
Steve Bennett
20
@SteveBennett: Je n'appellerais pas le fait que a = false; a ||= truene fait pas ce que votre réponse dit qu'il fait une "nuance".
Jörg W Mittag
23
Peut-être que cette question a été posée tant de fois parce que les gens continuent de répondre que cette question a été posée tant de fois.
einnocent
8
Avec cette réponse, il est facile de voir pourquoi il existe plusieurs threads. Si vous essayez de chercher une réponse à cette question en utilisant un chapeau novice, vous remarquerez que toutes les réponses ne sont pas claires. Par exemple, avec celui-ci, vous dites simplement ce qui ne l'est pas. Je suggère d'améliorer votre réponse et de donner des réponses faciles pour les novices: a = b à moins que
Arnold Roa
594

a ||= best un opérateur d'affectation conditionnelle . Cela signifie que si aest indéfini ou faux , alors évaluez bet définissez ale résultat . De manière équivalente, si aest défini et évalué comme véridique, alors bn'est pas évalué et aucune affectation n'a lieu. Par exemple:

a ||= nil # => nil
a ||= 0 # => 0
a ||= 2 # => 0

foo = false # => false
foo ||= true # => true
foo ||= false # => true

Confusément, il ressemble à d'autres opérateurs d'affectation (tels que +=), mais se comporte différemment.

  • a += b Se traduit par a = a + b
  • a ||= b se traduit à peu près par a || a = b

C'est un raccourci pour a || a = b. La différence est que, quand aest indéfini, a || a = baugmenterait NameError, alors qu'il se a ||= bmet aà b. Cette distinction est sans importance si aet bsont toutes deux des variables locales, mais elle est significative si l'une ou l'autre est une méthode getter / setter d'une classe.

Lectures complémentaires:

Steve Bennett
la source
52
Merci pour cette réponse, cela a beaucoup plus de sens.
Tom Hert
Je n'ai pas suffisamment cherché mais je ne comprends toujours pas pourquoi vous utiliseriez ceci par opposition à a = a || b. peut-être juste mon opinion personnelle mais un peu ridicule qu'une telle nuance existe ...
dtc
2
@dtc, réfléchissez h = Hash.new(0); h[1] ||= 2. Considérons maintenant les deux extensions possibles h[1] = h[1] || 2contre h[1] || h[1] = 2. Les deux expressions sont évaluées 0mais la première augmente inutilement la taille du hachage. C'est peut-être pour cela que Matz a choisi de se ||=comporter davantage comme la deuxième extension. (J'ai basé ceci sur un exemple d'un des fils liés à dans une autre réponse.)
antinome
1
J'aime l'autre réponse pour sa profondeur, mais j'adore cette réponse pour sa simplicité. Pour quelqu'un qui apprend Ruby, c'est le type de réponse dont nous avons besoin. Si nous savions ce que signifie || =, alors la question aurait probablement été formulée différemment.
OBCENEIKON
1
Pour info, a || a = bun NameErrorif an'est pas défini. a ||= bne le fait pas, mais l'initialise aet le définit à la place b. Pour autant que je sache, c'est la seule distinction entre les deux. De même, la seule différence entre a = a || bet a ||= bque je sache, c'est que si a=est une méthode, elle sera appelée indépendamment de ce qui aretourne. En outre, la seule différence entre a = b unless aet a ||= bque je sache, c'est que cette déclaration est évaluée au nillieu de asi aest véridique. Beaucoup d'approximations, mais rien de bien équivalent ...
Ajedi32
32

Réponse concise et complète

a ||= b

évalue la même manière que chacune des lignes suivantes

a || a = b
a ? a : a = b
if a then a else a = b end

-

D'autre part,

a = a || b

évalue la même manière que chacune des lignes suivantes

a = a ? a : b
if a then a = a else a = b end

-

Edit: Comme AJedi32 l'a souligné dans les commentaires, cela n'est vrai que si: 1. a est une variable définie. 2. L'évaluation d'une seule fois et de deux fois n'entraîne pas de différence dans l'état du programme ou du système.

the_minted
la source
1
êtes-vous sûr? Cela implique que si aest faux / zéro / non défini, il est évalué deux fois. (Mais je ne connais pas Ruby, donc je ne sais pas si les valeurs peuvent être 'évaluées' exactement ...)
Steve Bennett
Je vois ce que tu dis. Ce que je voulais dire par deux lignes équivalentes, c'est que l'état final sera équivalent après que la ligne entière a été évaluée, ce qui signifie la valeur de a, b et ce qui est retourné. Que les interprètes ruby ​​utilisent ou non différents états - comme plusieurs évaluations de a - pour y arriver est tout à fait possible. Des experts interprètes rubis là-bas?
the_minted
3
Ce n'est pas tout à fait ça. a || a = b, a ? a : a = b, if a then a else a = b endEt if a then a = a else a = b endjetteront une erreur si aest non défini, alors a ||= bet a = a || bnon. En outre, a || a = b, a ? a : a = b, if a then a else a = b end, a = a ? a : bet if a then a = a else a = b endévaluer adeux fois quand aest truthy, alors a ||= bet a = a || bne sont pas.
Ajedi32
1
* correction: a || a = bn'évaluera pas adeux fois quand ac'est vrai.
Ajedi32
1
@the_minted the end state will be equivalent after the whole line has been evaluatedCe n'est pas nécessairement vrai cependant. Et si ac'est une méthode? Les méthodes peuvent avoir des effets secondaires. Par exemple, avec public; def a=n; @a=n; end; def a; @a+=1; end; self.a = 5, self.a ||= breviendra 6, mais self.a ? self.a : self.a = breviendra 7.
Ajedi32
27

En bref, a||=bsignifie: Si aest undefined, nil or false, attribuez bà a. Sinon, gardez aintact.

vidang
la source
16
Fondamentalement,


x ||= y veux dire

si xa une valeur laisser seul et ne change pas la valeur, sinon mis xày

nPcomp
la source
13

Cela signifie ou égal à. Il vérifie si la valeur de gauche est définie, puis utilise cela. Si ce n'est pas le cas, utilisez la valeur à droite. Vous pouvez l'utiliser dans Rails pour mettre en cache les variables d'instance dans les modèles.

Un exemple rapide basé sur Rails, où nous créons une fonction pour récupérer l'utilisateur actuellement connecté:

class User > ActiveRecord::Base

  def current_user
    @current_user ||= User.find_by_id(session[:user_id])
  end

end

Il vérifie si la variable d'instance @current_user est définie. Si tel est le cas, il le renverra, économisant ainsi un appel de base de données. Si ce n'est pas le cas, nous appelons puis définissons la variable @current_user sur cela. C'est une technique de mise en cache très simple, mais elle est idéale lorsque vous récupérez plusieurs fois la même variable d'instance dans l'application.

Jamie Rumbelow
la source
8
C'est faux. Veuillez lire Ruby-Forum.Com/topic/151660 et les liens qui y sont fournis.
Jörg W Mittag
1
@Jo (tréma) rg, je ne vois pas ce qui ne va pas. Votre lien est une liste d'autres liens. Aucune véritable explication pourquoi c'est faux, sonne juste comme un jugement de valeur de votre côté.
eggmatters
cette réponse est fausse, car elle se déclenche non seulement sur undefined, mais aussi sur falseet nil, ce qui pourrait ne pas être pertinent pour current_user, mais surtout le falsepeut être inattendu dans d'autres cas
dfherr
Malgré le caractère incomplet de cette réponse (ne fonctionne pas pour néant / faux), c'est le premier qui explique pourquoi vous souhaitez utiliser || =, alors merci!
Jonathan Tuzman
8
x ||= y

est

x || x = y

"si x est faux ou indéfini, alors x pointe vers y"

Kiattisak Anoochitarom
la source
8

Pour être précis, a ||= bsignifie «si aest indéfini ou faux ( falseou nil), défini asur bet évalué à (c.-à-d. Retour) b, sinon évaluez àa ».

D'autres essaient souvent d'illustrer cela en disant que cela a ||= béquivaut à a || a = bou a = a || b. Ces équivalences peuvent être utiles pour comprendre le concept, mais sachez qu'elles ne sont pas exactes dans toutes les conditions. Permettez-moi d'expliquer:

  • a ||= ba || a = b ?

    Le comportement de ces instructions diffère lorsqu'il as'agit d'une variable locale non définie. Dans ce cas, a ||= bsera défini asur b(et évalué à b), tandis que a || a = baugmentera NameError: undefined local variable or method 'a' for main:Object.

  • a ||= ba = a || b ?

    L'équivalence de ces déclarations sont souvent considérées, car une équivalence similaire est vrai pour d' autres affectations abrégé opérateurs ( par exemple +=, -=, *=, /=, %=, **=, &=, |=, ^=, <<=et >>=). Cependant, ||=le comportement de ces déclarations peut différer quand a=est une méthode sur un objet et aest véridique. Dans ce cas, a ||= bne fera rien (autre que d'évaluer a), tandis que a = a || bfera appel a=(a)au arécepteur de. Comme d' autres l' ont souligné, cela peut faire une différence lorsque l'appel a=aa des effets secondaires, comme l'ajout de clés à un hachage.

  • a ||= ba = b unless a ??

    Le comportement de ces déclarations ne diffère que par ce à quoi elles évaluent la avéracité. Dans ce cas, a = b unless aévaluera à nil(mais ane sera toujours pas défini, comme prévu), tandis que a ||= bévaluera à a.

  • a ||= bdefined?(a) ? (a || a = b) : (a = b) ????

    Toujours pas. Ces instructions peuvent différer lorsqu'une method_missingméthode existe qui renvoie une valeur véridique pour a. Dans ce cas, a ||= bévaluera à tout method_missingretour et ne tentera pas de définir a, tandis que defined?(a) ? (a || a = b) : (a = b)définira asur bet évaluera sur b.

D'accord, d'accord, alors qu'est - ce a ||= b équivalent? Existe-t-il un moyen d'exprimer cela en Ruby?

Eh bien, en supposant que je ne néglige rien, je pense que a ||= bc'est fonctionnellement équivalent à ... ( drumroll )

begin
  a = nil if false
  a || a = b
end

Attendez! N'est-ce pas juste le premier exemple avec un noop devant lui? Enfin, pas tout à fait. Rappelez-vous comment j'ai dit auparavant que ce a ||= bn'est pas équivalent à a || a = bquand aest une variable locale non définie? Eh bien, a = nil if falseassure que ce an'est jamais indéfini, même si cette ligne n'est jamais exécutée. Les variables locales dans Ruby ont une portée lexicale.

Ajedi32
la source
Donc, votre troisième exemple étendu:(a=b unless a) or a
vol7ron
1
@ vol7ron Cela a un problème similaire à # 2. Si aest une méthode, elle sera appelée deux fois au lieu d'une fois (si elle retourne une valeur véridique la première fois). Cela pourrait entraîner des comportements différents si, par exemple, il afaut du temps pour revenir ou s'il a des effets secondaires.
Ajedi32
De plus, la première phrase, ne devrait-elle pas dire assigner bàa , le rhs n'assigne-t-il pas encore au lhs, ou en d'autres termes, le lhs ne fixe-t-il toujours pas sa valeur au rhs?
vol7ron
Meilleure a ||= bréponse que j'ai trouvée sur Internet. Merci.
Eric Duminil
3

unless x x = y end

sauf si x a une valeur (elle n'est ni nulle ni fausse), définissez-la égale à y

est équivalent à

x ||= y

0r4cl3
la source
3

Supposer a = 2 etb = 3

ALORS, a ||= b sera ramené à ala valeur de2 .

Comme quand un évalue à une certaine valeur non résultée en falseou nil.. C'est pourquoi il lln'évalue pasb la valeur de.

Supposons maintenant a = niletb = 3 .

Ensuite, a ||= bil en résultera 3ieb .

Comme il essaie d'abord d'évaluer la valeur de a qui a abouti à nil.. il a donc évaluéb la valeur de

Le meilleur exemple utilisé dans l'application ror est:

#To get currently logged in iser
def current_user
  @current_user ||= User.find_by_id(session[:user_id])
end

# Make current_user available in templates as a helper
helper_method :current_user

Où, User.find_by_id(session[:user_id])est renvoyé si et seulement si @current_usern'est pas initialisé auparavant.

Pankhuri
la source
3

a || = b

Signifie si une valeur est présente dans 'a' et que vous ne voulez pas la modifier en utilisant cette valeur, sinon si 'a' n'a pas de valeur, utilisez la valeur de 'b'.

Des mots simples, si côté gauche sinon nul, pointent vers la valeur existante, sinon pointent vers la valeur du côté droit.

SHUBHAM SHARMA
la source
2
a ||= b

est équivalent à

a || a = b

et pas

a = a || b

en raison de la situation où vous définissez un hachage avec une valeur par défaut (le hachage renverra la valeur par défaut pour toutes les clés non définies)

a = Hash.new(true) #Which is: {}

si tu utilises:

a[10] ||= 10 #same as a[10] || a[10] = 10

a est toujours:

{}

mais quand tu l'écris comme ça:

a[10] = a[10] || 10

a devient:

{10 => true}

parce que vous avez attribué la valeur de lui-même à la clé 10, qui est par défaut true, donc maintenant le hachage est défini pour la clé 10, plutôt que de ne jamais effectuer l'affectation en premier lieu.

RileyE
la source
2

C'est comme une instanciation paresseuse. Si la variable est déjà définie, elle prendra cette valeur au lieu de la recréer.

mukh007
la source
2

N'oubliez pas que ce ||=n'est pas une opération atomique et donc, ce n'est pas sûr pour les threads. En règle générale, ne l'utilisez pas pour les méthodes de classe.

Luca Guidi
la source
2

Ceci est la notation d'affectation par défaut

par exemple: x || = 1,
cela vérifiera si x est nul ou non. Si x est bien nul, il lui attribuera alors cette nouvelle valeur (1 dans notre exemple)

plus explicite:
si x == nul
x = 1
fin

Max Rogers
la source
soit nilou falsepas seulementnil
Alex Poca
2

|| = est un opérateur d'affectation conditionnelle

  x ||= y

est équivalent à

  x = x || y

Ou bien

if defined?(x) and x
    x = x
else 
    x = y
end
Sunda
la source
2

Si Xn'a PAS de valeur, la valeur de lui sera attribuée Y. Sinon, il conservera sa valeur d'origine, 5 dans cet exemple:

irb(main):020:0> x = 5
=> 5
irb(main):021:0> y = 10
=> 10
irb(main):022:0> x ||= y
=> 5

# Now set x to nil. 

irb(main):025:0> x = nil
=> nil
irb(main):026:0> x ||= y
=> 10
zee
la source
1

Comme une idée fausse commune, a ||= bn'est pas équivalent à a = a || b, mais il se comporte comme a || a = b.

Mais voici un cas délicat. Si an'est pas défini, a || a = 42augmente NameError, tandis que a ||= 42retourne 42. Donc, ils ne semblent pas être des expressions équivalentes.

tessie
la source
1

||= affecte la valeur à droite uniquement si left == nil (ou est indéfini ou faux).

H. Almidan
la source
vous vouliez probablement dire «attribue une valeur à gauche» au lieu de droite
Maysam Torabi
0
irb(main):001:0> a = 1
=> 1
irb(main):002:0> a ||= 2
=> 1

Parce que aétait déjà réglé sur1

irb(main):003:0> a = nil
=> nil
irb(main):004:0> a ||= 2
=> 2

parce aétaitnil

fulvio
la source
Quelle est la date de réponse ici. Pourquoi ne montre-t-il pas l'année?
Shiv
0
b = 5
a ||= b

Cela se traduit par:

a = a || b

qui sera

a = nil || 5

donc finalement

a = 5

Maintenant, si vous appelez à nouveau:

a ||= b
a = a || b
a = 5 || 5
a = 5

b = 6

Maintenant, si vous appelez à nouveau:

a ||= b
a = a || b
a = 5 || 6
a = 5 

Si vous observez, baucune valeur ne sera affectée à a. aaura toujours 5.

C'est un modèle de mémorisation utilisé dans Ruby pour accélérer les accesseurs.

def users
  @users ||= User.all
end

Cela se traduit essentiellement par:

@users = @users || User.all

Vous allez donc appeler la base de données pour la première fois que vous appelez cette méthode.

Les appels futurs à cette méthode renverront simplement la valeur de la @usersvariable d'instance.

siva krishna reddy
la source
0

||= est appelé un opérateur d'affectation conditionnelle.

Cela fonctionne essentiellement comme =mais à l'exception que si une variable a déjà été affectée, elle ne fera rien.

Premier exemple:

x ||= 10

Deuxième exemple:

x = 20
x ||= 10

Dans le premier exemple xest maintenant égal à 10. Cependant, dans le deuxième exemple xest déjà défini comme 20. L'opérateur conditionnel n'a donc aucun effet. xa encore 20 ans après avoir couru x ||= 10.

Charlie Wood
la source
-2

a ||= brevient à dire a = b if a.nil?oua = b unless a

Mais les 3 options affichent-elles les mêmes performances? Avec Ruby 2.5.1 cela

1000000.times do
  a ||= 1
  a ||= 1
  a ||= 1
  a ||= 1
  a ||= 1
  a ||= 1
  a ||= 1
  a ||= 1
  a ||= 1
  a ||= 1
end

prend 0,099 secondes sur mon PC, tandis que

1000000.times do
  a = 1 unless a
  a = 1 unless a
  a = 1 unless a
  a = 1 unless a
  a = 1 unless a
  a = 1 unless a
  a = 1 unless a
  a = 1 unless a
  a = 1 unless a
  a = 1 unless a
end

prend 0.062 Secondes. C'est presque 40% plus rapide.

et puis nous avons aussi:

1000000.times do
  a = 1 if a.nil?
  a = 1 if a.nil?
  a = 1 if a.nil?
  a = 1 if a.nil?
  a = 1 if a.nil?
  a = 1 if a.nil?
  a = 1 if a.nil?
  a = 1 if a.nil?
  a = 1 if a.nil?
  a = 1 if a.nil?
end

ce qui prend 0,166 seconde.

Non pas que cela aura un impact significatif sur les performances en général, mais si vous avez besoin de ce dernier bit d'optimisation, considérez ce résultat. Soit dit en passant: a = 1 unless aest plus facile à lire pour le novice, il est explicite.

Remarque 1: la raison pour laquelle la ligne d'affectation est répétée plusieurs fois est de réduire la surcharge de la boucle sur le temps mesuré.

Note 2: Les résultats sont similaires si je fais a=nilzéro avant chaque affectation.

Ymox
la source