Динамическая область видимости переменной в Ruby 1.9 - PullRequest
6 голосов
/ 22 марта 2011

Меня интересует использование динамических (в отличие от лексических) переменных в Ruby.

Кажется, что нет прямого встроенного способа, как с let в Лиспе. Кристиан Нойкирхен предлагает один из возможных способов создания динамической переменной области действия.Он создает «локальный хэш потока» в своем классе Dynamic.Я не был в восторге от этого.

Тогда я вспомнил, что в Ruby 1.9 есть метод tap.Я вижу много людей, использующих tap для печати значений отладки в цепочке команд.Я думаю, что это может быть использовано для имитации динамически изменяемой переменной.

Ниже приведен пример ситуации, в которой нужно использовать динамическую переменную с ограничением, и решение с использованием tap.

Если бы у меня был блог, чтобы опубликовать это и получить отзывы, я бы сделал это там.Вместо этого я прихожу в S / O за критику этой идеи.Опубликуйте критику, и я дам правильный ответ тому, у кого больше голосов.


Ситуация

У вас есть объект ActiveRecord, представляющий Account, каждая учетная запись has_many Transaction с.A Transaction имеет два атрибута:

  • description
  • amount

Вы хотите найти сумму всех transactions наaccount, имея в виду, что amount может быть либо nil, либо Float (нет, вы не можете критиковать это).

Ваша первая идея:

def account_value
  transactions.inject(0){|acum, t| acum += t.amount}
end

Это бомбы, когда вы получаете нулевое количество в первый раз:

TypeError: nil can't be coerced into Fixnum

Чистый раствор

Используйте tap для временного определения amount = 0.Мы хотим, чтобы это было временным только в том случае, если мы забыли установить его обратно и сохранить transaction с сохранением значения 0.

def account_value
  transactions.inject(0){|acm, t| t.amount.tap{|amount| amount ||=0; acm+=amount}; acm}
end

Поскольку значение присваивания равно нулю, если нольamount находится в пределах tap, нам не нужно беспокоиться о том, чтобы забыть установить его на nil.

Что вы думаете?

Ответы [ 3 ]

6 голосов
/ 22 марта 2011

Ну, я думаю, что вы стремитесь к чему-то другому, но следующий код исправляет ваш пример и на самом деле его легче понять:

transactions.inject(0) { |acum, t| acum += t.amount || 0 }

Но я не думаю, что суммирование методовдля сумм следует знать о значении по умолчанию для nil сумм, поэтому (даже если ваш вопрос утверждает, что я не могу с ним спорить), я бы изменил метод amount, чтобы вместо него возвращалось значение по умолчанию:

def amount
  @amount || 0
end

Тем не менее, я думаю, что ваш пример слишком прост для решения, и вы на самом деле стремитесь к ответам на более сложный вопрос.Ждем всех остальных ответов.

4 голосов
/ 22 марта 2011

Я не вижу, где находится динамическая область действия в вашем решении. tap вводит новый блок с лексической областью, значения восстанавливаются в соответствии с лексической областью действия.

Кстати, let в Common Lisp тоже не создает динамически изменяемые переменные. Вы должны declare переменные special, чтобы это произошло (или оно будет динамически переопределять значение переменной, если эта переменная уже special).

РЕДАКТИРОВАТЬ: Ради полноты я быстро реализовал класс, который реализует фактическое поведение динамической переменной: http://pastie.org/1700111

Вывод этого:

foo
bar
foo

РЕДАКТИРОВАНИЕ 2: Вот еще одна реализация, которая делает это для переменных экземпляра без необходимости использования класса-оболочки: http://pastie.org/1706102

2 голосов
/ 22 марта 2011

Указанную проблему можно решить с помощью оператора || (как показано в Rubii).

Вы можете еще больше упростить это, вызвав метод sum в массиве.

account.transactions.all.sum {|t| t.amount|| 0 }

С другой стороны, групповые вычисления не должны выполняться в Ruby. БД должна сделать всю тяжелую работу.

account.transactions.sum(:amount) # SELECT SUM(amount)
                                  # FROM   transactions
                                  # WHERE  account_id = account.id
...