Ruby: возобновляемые функции с аргументами - PullRequest
5 голосов
/ 22 февраля 2010

Мне нужна функция, которая поддерживает локальное состояние в Ruby. Каждый раз, когда я вызываю функцию, я хочу вернуть результат, который зависит как от вызывающего аргумента, так и от сохраненного состояния функции. Вот простой пример:

def inc_mult(factor)
  @state ||= 0   # initialize the state the first time.
  @state += 1    # adjust the internal state.
  factor * @state
end

Обратите внимание, что состояние инициализируется в первый раз, но последующие вызовы обращаются к сохраненному состоянию. Это хорошо, за исключением того, что @state просачивается в окружающий контекст, чего я не хочу.

Какой самый элегантный способ переписать это так, чтобы @state не протекал?

(Примечание: мой реальный пример гораздо более сложный, и инициализация состояние дорого.)

Ответы [ 6 ]

4 голосов
/ 22 февраля 2010

Возможно, вы хотите инкапсулировать inc_mult в его собственный класс, поскольку вы хотите инкапсулировать его состояние отдельно от содержащего его объекта. Так работают генераторы (оператор yield) в Python и C #.

Что-то простое, как это сделало бы:

class Foo 
  state = 0 
  define_method(:[]) do |factor|
    state += 1
    factor * state
  end 
end

С философской точки зрения я думаю, что то, к чему вы стремитесь, несовместимо с тем, что Ruby рассматривает методы как сообщения, а не как функции, которые могут быть несколько самостоятельными.

2 голосов
/ 22 февраля 2010

Функции не сохраняют состояние. Это процессуальный кодекс. Классы содержат как государственный, так и процедурный код. Самый элегантный способ сделать это - следовать правильной парадигме программирования:

Класс для поддержания состояния
Функция для управления состоянием

Поскольку вы используете Ruby, вам может показаться немного более удобным поместить эти вещи в модуль, который может быть включен. Модуль может обрабатывать поддержание состояния, и метод может быть просто вызван через:

require 'incmodule'
IncModule::inc_mult(10)

или что-то похожее

1 голос
/ 22 февраля 2010

Я хочу функцию , которая поддерживает локальное состояние в 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
0 голосов
/ 14 августа 2013

вы можете использовать лямбду. например,

$ cat test.rb

def mk_lambda( init = 0 )
  state = init
  ->(factor=1, incr=nil){ 
    state += incr || 1;
    puts "state now is: #{state}"
    factor * state
  }
end

f = mk_lambda

p f[]
p f[1]
p f[2]
p f[100]
p f[100,50]
p f[100]

$ ruby ​​test.rb

state now is: 1
1
state now is: 2
2
state now is: 3
6
state now is: 4
400
state now is: 54
5400
state now is: 55
5500

С уважением -botp

0 голосов
/ 22 февраля 2010

Ну, вы могли бы немного поиграть ... А как насчет функции, которая переписывает себя?

def imult(factor)
  state = 1;
  rewrite_with_state(state+1)
  factor*state
end

def rewrite_with_state(state)
  eval "def imult(factor); state = #{state}; rewrite_with_state(#{state+1}); factor*state; end;"
end

Предупреждение: это очень уродливо и не должно использоваться в рабочем коде!

0 голосов
/ 22 февраля 2010

Кажется, что вы могли бы просто использовать глобальную переменную или переменную класса в каком-то другом классе, который, по крайней мере, позволил бы вам пропускать непосредственно окружающий контекст.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...