2010-02-22 3 views
5

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

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

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

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

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

+0

Я не слишком хорошо знакомы с ними, но ISN» Это продолжение? Я бы заглянул в 'callcc' для получения дополнительной информации. –

ответ

4

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

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

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

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

+0

да, я думаю, вы (и @Mike Trpcic) правы, что я хочу сделать, это не философски правильно ... очень интересно думать! – Peter

0

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

+0

уверен, но это не очень элегантно ... кажется, что 'inc_mult' должен уметь следить за своим состоянием. – Peter

2

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

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

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

require 'incmodule' 
IncModule::inc_mult(10) 

Или что-то подобное

+2

Чтобы быть разборчивым, ** объекты ** содержит состояние и имеют ** методы ** для управления состоянием. –

+3

@John Topley: Когда я читаю или слышу что-то подобное, я всегда рисую сержант. Хартман из Full Metal Jacket: «Это называется * объект * -ориентированный не * класс * -ориентированный, черт возьми! Drop and gimme 50, maggot!" Поверьте мне, это помогает разочарованию. –

0

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

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 

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

1

Я хочу Функция, которая хранит локальное состояние в 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

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

$ кошка 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] 

$ рубин 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

Смежные вопросы