Reputation: 132387
I want a function that keeps local state in Ruby. Each time I call the function I want to return a result that depends both on a calling argument and on the function's stored state. Here's a simple example:
def inc_mult(factor)
@state ||= 0 # initialize the state the first time.
@state += 1 # adjust the internal state.
factor * @state
end
Note that the state is initialized the first time, but subsequent calls access stored state. This is good, except that @state
leaks into the surrounding context, which I don't want.
What is the most elegant way of rewriting this so that @state
doesn't leak?
(Note: My actual example is much more complicated, and initializing the state is expensive.)
Upvotes: 5
Views: 234
Reputation: 31
you can use lambda. eg,
$ 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
kind regards -botp
Upvotes: 0
Reputation: 369604
I want a function that keeps local state in Ruby.
That word "function" should immediately raise a big fat red flashing warning sign that you are using the wrong programming language. If you want functions, you should use a functional programming language, not an object-oriented one. In a functional programming language, functions usually close over their lexical environment, which makes what you are trying to do absolutely trivial:
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
This particular example is in ECMAScript, but it looks more or less the same in any functional programming language.
[Note: I'm aware that it's not a very good example, because ECMAScript is actually also an object-oriented language and because it has broken scope semantics that atually mean that state
leaks in this case, too. In a language with proper scope semantics (and in a couple of years, ECMAScript will be one of them), this'll work as intended. I used ECMAScript mainly for its familiar syntax, not as an example of a good functional language.]
This is the way that state is encapsulated in functional languages since, well, since there are functional languages, all the way back to lambda calculus.
However, in the 1960s some clever people noticed that this was a very common pattern, and they decided that this pattern was so common that it deserved its own language feature. And thus, the object was born.
So, in an object-oriented language, instead of using functional closures to encapsulate state, you would use objects. As you may have noticed, methods in Ruby don't close over their lexical environment, unlike functions in functional programming languages. And this is precisely the reason: because encapsulation of state is achieved via other means.
So, in Ruby you would use an object like this:
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: This 1:1 correspondence is what functional programmers are talking about when they say "objects are just a poor man's closures". Of course, object-oriented programmers usually counter with "closures are just a poor man's objects". And the funny thing is, both of them are right and neither of them realize it.]
Now, for completeness' sake I want to point out that while methods don't close over their lexical environment, there is one construct in Ruby, which does: blocks. (Interestingly enough, blocks aren't objects.) And, since you can define methods using blocks, you can also define methods which are closures:
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
Upvotes: 1
Reputation: 10268
Well, you could play around a bit... What about a function that rewrites itself?
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
Warning: This is extremely ugly and should not be used in production code!
Upvotes: 0
Reputation: 25669
Functions are stateless. They are procedural code. Classes contain state as well as procedural code. The most elegant way to do this would be to follow the proper programming paradigm:
Class to maintain state
Function to manipulate state
Since you're using Ruby, it may seem a bit more elegent to you to put these things in a module that can be included. The module can handle maintaining state, and the method could simply be called via:
require 'incmodule'
IncModule::inc_mult(10)
Or something similar
Upvotes: 2
Reputation: 177875
You probably want to encapsulate inc_mult
into its own class, since you want to encapsulate its state separately from its containing object. This is how generators (the yield
statement) work in Python and C#.
Something as simple as this would do it:
class Foo
state = 0
define_method(:[]) do |factor|
state += 1
factor * state
end
end
Philosophically, I think what you’re aiming for is incompatible with Ruby’s view of methods as messages, rather than as functions that can somewhat stand alone.
Upvotes: 4
Reputation: 146231
It seems like you could just use a global or a class variable in some other class, which would at least allow you to skip over the immediately surrounding context.
Upvotes: 0