Я хочу функцию , которая поддерживает локальное состояние в Ruby.
Это слово "функция" должно немедленно поднять большой жирный красный мигающий предупреждающий знак о том, что вы используете неправильный язык программирования. Если вам нужны функции, вы должны использовать функциональный язык программирования, а не объектно-ориентированный. В функциональном языке программирования функции обычно закрываются в своей лексической среде, что делает то, что вы пытаетесь сделать, абсолютно тривиальным:
var state;
function incMult(factor) {
if (state === undefined) {
state = 0;
}
state += 1;
return factor * state;
}
print(incMult(2)); // => 2
print(incMult(2)); // => 4
print(incMult(2)); // => 6
Этот конкретный пример есть в ECMAScript, но он выглядит более или менее одинаково в любом функциональном языке программирования.
[Примечание: я знаю, что это не очень хороший пример, потому что ECMAScript на самом деле также является объектно-ориентированным языком и потому что он имеет нарушенную семантику контекста, что в действительности означает, что state
в этом случае также просачивается. На языке с правильной семантикой области действия (и через пару лет ECMAScript будет одним из них), это будет работать как задумано. Я использовал ECMAScript главным образом для его знакомого синтаксиса, а не в качестве примера хорошего функционального языка.]
Это способ, которым состояние инкапсулируется в функциональных языках, поскольку, поскольку функциональные языки существуют, вплоть до лямбда-исчисления.
Однако в 1960-е годы некоторые умные люди заметили, что это очень распространенный шаблон, и решили, что этот шаблон настолько распространен, что он заслуживает своего собственного языка. И, таким образом, объект родился.
Таким образом, в объектно-ориентированном языке вместо использования функциональных замыканий для инкапсуляции состояния вы бы использовали объекты. Как вы могли заметить, методы в Ruby не закрываются в своей лексической среде, в отличие от функций в функциональных языках программирования. И это как раз и есть причина: инкапсуляция состояния достигается другими способами.
Итак, в Ruby вы бы использовали такой объект:
inc_mult = Object.new
def inc_mult.call(factor)
@state ||= 0
@state += 1
factor * @state
end
p inc_mult.(2) # => 2
p inc_mult.(2) # => 4
p inc_mult.(2) # => 6
[Sidenote: Это соответствие 1: 1 - это то, о чем говорят функциональные программисты, когда говорят, что «объекты - это просто замыкания бедняков». Конечно, объектно-ориентированные программисты обычно противостоят "замыканиям - просто объекты бедняков". И самое смешное, они оба правы, и никто из них не осознает этого.]
Теперь, для полноты картины, хочу отметить, что хотя методы не закрываются в своей лексической среде, в Ruby есть одна конструкция, которая делает : блоки. (Интересно, что блоки не являются объектами.) И, поскольку вы можете определять методы, используя блоки, вы также можете определять методы, которые являются замыканиями:
foo = Object.new
state = nil
foo.define_singleton_method :inc_mult do |factor|
state ||= 0
state += 1
factor * state
end
p foo.inc_mult(2) # => 2
p foo.inc_mult(2) # => 4
p foo.inc_mult(2) # => 6