Scoping de variable dynamique dans Ruby 1.9
-
28-10-2019 - |
Question
Je suis intéressé par l'utilisation de variables de portée dynamique (par opposition à lexicale) dans Ruby.
Il semble qu'il n'y ait pas de méthode intégrée directe, comme avec let
en Lisp. Une façon possible de faire une variable de portée dynamique est suggérée par Christian Neukirchen . Il crée un «hachage local de thread» dans sa classe Dynamic
. Je n'étais pas trop fou à ce sujet.
Ensuite, je me suis souvenu que Ruby 1.9 avait une méthode tap
. Je vois beaucoup de gens utiliser tap
pour imprimer des valeurs de débogage dans une chaîne de commandes. Je pense qu'il peut être utilisé pour imiter très bien une variable à portée dynamique.
Voici un exemple de situation dans laquelle on voudrait utiliser une variable de portée dynamique, et une solution utilisant tap
.
Si j'avais un blog sur lequel publier ceci et obtenir des commentaires, je le ferais là-bas. Au lieu de cela, je viens à S / O pour la critique de cette idée. Postez votre critique, je donnerai la bonne réponse à celle avec le plus de votes positifs.
Situation
Vous avez un objet ActiveRecord représentant un Account
, chaque compte has_many
Transaction
s. Un Transaction
a deux attributs:
-
description
-
amount
Vous voulez trouver la somme de tous les transactions
sur le account
, en gardant à l'esprit que amount
peut être soit nil
, soit un Float
(non, vous ne pouvez pas le critiquer).
Votre première idée est:
def account_value
transactions.inject(0){|acum, t| acum += t.amount}
end
Cette bombe la première fois que vous avez un montant nul:
TypeError: nil can't be coerced into Fixnum
Solution propre
Utilisez tap
pour définir temporairement amount = 0
. Nous voulons que cela soit temporaire uniquement au cas où nous oublierions de le réinitialiser et de sauvegarder le transaction
avec la valeur 0 toujours en place.
def account_value
transactions.inject(0){|acm, t| t.amount.tap{|amount| amount ||=0; acm+=amount}; acm}
end
Puisque l'affectation à zéro si nul de amount
se trouve dans le bloc tap
, nous n'avons pas à nous soucier d'oublier de le remettre à nil
.
Que pensez-vous?
La solution
Eh bien, je pense que vous visez autre chose, mais le code suivant corrige votre exemple et est en fait plus facile à comprendre:
transactions.inject(0) { |acum, t| acum += t.amount || 0 }
Mais je ne pense pas que la méthode de récapitulation des montants doive connaître la valeur par défaut des montants nil
, donc (même si votre question indique que je ne peux pas discuter avec elle) je changerais la méthode amount
pour renvoyer la valeur par défaut à la place:
def amount
@amount || 0
end
Néanmoins, je pense que votre exemple est trop facile à résoudre et que vous visez en fait des réponses à une question plus complexe.Dans l'attente de toutes les autres réponses.
Autres conseils
Je ne vois pas où se situe la portée dynamique de votre solution.tap
introduit un nouveau bloc à portée lexicale, les valeurs sont restaurées selon la portée lexicale.
BTW, le let
en Common Lisp ne crée pas non plus de variables à portée dynamique par lui-même.Vous devez declare
les variables special
pour que cela se produise (ou il redéfinira la valeur d'une variable dynamiquement si cette variable est déjà special
).
EDIT: Par souci d'exhaustivité, j'ai rapidement implémenté une classe qui implémente le comportement réel des variables dynamiques: http:// pastie.org / 1700111
Le résultat est:
foo
bar
foo
EDIT 2: Voici une autre implémentation qui fait cela pour les variables d'instance sans avoir besoin d'une classe wrapper: http:// pastie.org / 1706102
Le problème indiqué peut être résolu en utilisant l'opérateur ||
(comme indiqué par rubii).
Vous pouvez encore simplifier cela en appelant la méthode sum
sur le tableau.
account.transactions.all.sum {|t| t.amount|| 0 }
D'un autre côté, les calculs de groupe ne doivent pas être effectués dans Ruby.La DB devrait faire tout le gros du travail.
account.transactions.sum(:amount) # SELECT SUM(amount)
# FROM transactions
# WHERE account_id = account.id