Discovering self-evaluating objects. (pt. 1 of 2)

by Rabbit

Notice: This is part one of a two-part post. Read the second post when you’re done with this one.

Have you ever written a program that sells products? Did you have to write the pricing rules for those products? Was it difficult? Time-consuming? Constantly changing? Screwing up your code and frustrating you to no end? Did people dip their grimy hands into your beautiful algorithm by asking you to make (gasp!) exceptions for this or that product? And don’t forget that special affiliate that everyone loves! He gets an extra percentage on top of everything else.

Stop. Breathe. Calm down. Everything will be okay. Object thinking to the rescue. Introducing the self-evaluating rule.

David West, author of Object Thinking, describes a self-evaluating rule thusly:

…think of a self-evaluating rule as an expression in the form of an ordered sequence of operators, variables, and constants. When the rule is asked to evaluate itself, each of its variables is asked to instantiate itself (become a known value), after which the rule resolves itself (applies the operators in the expression) to a final value — a Boolean true or false, for example.

Let’s take a stab at that, shall we? (You can paste the whole thing into IRB if you want to follow along.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class SelfEvaluatingRule
 
  attr_accessor :price, :tax
 
  def evaluate
    (@price * @tax) + @price
  end
 
end
 
rule = SelfEvaluatingRule.new
rule.price = 100
rule.tax = 0.05
rule.evaluate # 105.0

Interesting. Okay, not really. It might resemble something you’ve written before, but instead of calling the evaluate method, you called price (or something similar).

Let’s think for a moment… What we have is a collection of variables, and a hard-coded formula that puts those variables to use. Right now it’s pretty crappy code. How can we make it better?

  1. Enter variable names and values dynamically
  2. Enter arbitrary rules
  3. Evaluate changes to our variables and rules without rewriting code

Those sound like solid goals. We’re going to take another stab at it, but before we do, I’d like to show you something that might be new to you…

First, fire up IRB.

Now, type the following, then hit enter:

1
eval "1 + 1"

What did you get? 2? Pretty cool, eh? This simple trick forms the basis of everything else we’re going to do.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class SelfEvaluatingRule
 
  attr_accessor :price, :tax
 
  def evaluate(rule)
   eval(rule.gsub('price', @price.to_s).gsub('tax', @tax.to_s))
  end
 
end
 
rule = SelfEvaluatingRule.new
rule.price = 100
rule.tax = 0.05
rule.evaluate('(price * tax) + price') # 105.5

Not bad. We can now write dynamic rules that accept any values for price and tax, and use them in any formula we want, without having to rewrite our code. So let’s cross out number two on our list of enhancements.

Now let’s tackle those variables…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class SelfEvaluatingRule
 
 def initialize(rule)
   @rule = rule
 end
 
 def variables=(variables)
   @variables = variables
 end
 
 def evaluate
   @variables.each do |var, value|
     @rule.gsub!(var, value.to_s)
   end
 
   eval(@rule)
 rescue
   raise StandardError, 'Failed to evaluate'
 end
 
end
 
rule = SelfEvaluatingRule.new('(price * tax) + price')
rule.variables = { 'price' => 100, 'tax' => 0.05 }
rule.evaluate # 105

See what happened there? We extended our object by giving it the ability (more at responsibility) of maintaining an arbitrary collection of variables. We also specify the rule, though now we’re doing it during instantiation. Is this the best way to do it? Probably not; I can think of at least one more enhancement I’d make to the way rules are handled. What about you? Do you see room for improvement? I encourage you to challenge yourself and make it better!

With this last iteration we have completed all three of our stated goals. Not bad for just a few minutes work, eh?

So there you have it: the basics of a self-evaluating object. It sure feels like we’ve come a long way, but there’s more. Lots more. Currently we’re working with a single rule. However, chances are good that if you have need to write one rule, you’ve need to write many rules. And of course, we’ve been playing around in IRB. What if want to save rules and use them later? Perhaps attach those rules to other objects? There’s a world of possibilities out there!

I’ve written a solution in Rails that handles multiple rules, and even evaluates rules within rules. It’s a pretty neat solution. Though, after writing this post, I’m certain it can be cleaned up considerably.

I’m not sure what part two of this post will contain… whether I will simply post and explain the code I’ve already written, or try to refactor it first and then post. Time shall tell.