Peter
Peter

Reputation: 132387

Ruby: Resumable functions with arguments

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

Answers (6)

botp
botp

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

Jörg W Mittag
Jörg W Mittag

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

severin
severin

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

Mike Trpcic
Mike Trpcic

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

Josh Lee
Josh Lee

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

DigitalRoss
DigitalRoss

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

Related Questions