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 Transactions. 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?

Était-ce utile?

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 amountpour 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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top