{ |one, step, back| } 96 to 105 of 193 articles Syndicate: full/short

Running Back Versions of Gem-installed Applications   04 Mar 05
[ print link all ]
I keep forgetting that RubyGems has this capability. In this example, I have 7 (yes, seven, really) versions of rake installed.
  $ gem list --local rake

  *** LOCAL GEMS ***

  rake (0.4.15.1, 0.4.15, 0.4.14, 0.4.13, 0.4.12.1, 0.4.12, 0.4.4)
      Ruby based make-like utility.
  $
  $ rake --version
  rake, version 0.4.15.1
  $
  $ rake _0.4.12_ --version
  rake, version 0.4.12

Since rake is installed as a gem, you can run the executable of any installed version. By default, gems will run the latest one. But if you wish to run an older version, gems will accomodate you.


comments

XP Cincy Looks at Ruby   10 Feb 05
[ print link all ]
So, what happened when the Cincinnati XP Users group started looking at Ruby? Here's a quick report on the evening (with pictures!). See XpCincyAndRuby for background information.

XP and Ruby

As I mentioned in XpCincyAndRuby, the Cincinnati XP Users Group is all set to take a look at Ruby this month.



Mark Windholtz got us started with introductions (we had some newcomers to the group!) and laid out the plan for the evening.



We thought we would start off with something a bit fun, and something that would allow us to get into Ruby fairly quickly. I had adapted the "Paper, Rock, Scissors" RubyQuiz of the week before into something that could be run over DRb. The goal was to run Paper/Rock/Scissors players on our laptops, coordinating with a central game server (using DRb and Rinda).



The result was quite fun. As you can see, at least one of the programs was quite explicit about the state of its own playing ability (click to enlarge the pictures).



Mystery Guest

We did have one guest who was obviously a bit camera shy. Must have something to do with the project he is currently working on. It seems Chris has reached some good milestones recently and had time to come over and check out the Rails crowd. Welcome Chris! And don't worry, we won't tell any of your Java friends (wink, wink).


After the Meeting

Afterwards, we all headed to Unos and enjoyed some appetizers and drinks. This is where all the important discussions happen.

Next month we will be diving into Rails itself. If you are in the Cincinnati area, feel free to stop by and join us. We meet the first Tuesday of every Month.

Cheers!


comments

Web Applications   10 Feb 05
[ print link all ]
What’s up with all the cool web apps lately? Here’s some more that I ran across in the past few days.
TagSurf (tagsurf.com)
I’m not sure how to describe this. It is kinda like a web forum with tags applied to each message. What makes it really interesting is that if you use a URL for a tag, then the posting is "about" that web page. So, if you wish to talk about this posting (yes, the one you are reading right now), then create a message with the link onestepback.org/index.cgi/Tech/Web/MoreWebApps.rdoc as a tag. Or, even easier, click the "tagsurf-it" link in the article banner above and be taken immediately to TagSurf with the tags prefilled and ready to go. (I’ve also added a TagSurf link below marked for feedback). Give it a try.

Yes, you will have to sign up for TagSurf, but it is free. It is also very alpha, so be warned. But it is definitely cool. Read about how TagSurf came to be at Russell Beattie's Blog.

Google Maps: (maps.google.com/)
Try it, you’ll like it!

Nuff said.

Ta-Da Lists: (tadalists.com)
I’ve mentioned Ta-Da lists before, but I started using it as a temporary holding area for some ideas for an article on "10 Things Java Programmers Should Know About Ruby". I just dumped suggestions on to the list with minimal editting. Then I made the list public so that folks could see what had already been suggested.

The list wasn’t up two days before it was noticed by some Python guy who picked up on the "Fixes what’s wrong in Python" link (ack … exactly the message I was not trying to send, and it wasn’t even aimed at Python). Then a Smalltalk guy (hi Avi!) blogs about it. I’ve gotten more attention on this article (which I haven’t written yet) than most articles I actually do write.

Part of the problem was that there was no place in the Ta-Da list to explain its purpose. It was just a raw list of items without any context. Given the title, I can see why some folks assumed it was the finished product.

So I bemoaned the problem of no list description in Ta-Da. And then today, as I was editing another list, I noticed that the Ta-Da lists now do have a description field. Wow! Did I miss that? I would have sworn it wasn’t there before. Perhaps did the Ta-Da folk read my mind and knew exactly what I wanted. Anyways, I’m impressed. Thanks guys.

So, anyways, my apologies to the Python crowd. The "10 Things …" list was never targeted at Python and was an unedited compilation of feedback I had received. As Ian (the Python guy) points out a comment …

For Python and Ruby to try to take users from each other is a losing game — we’re both small players, and there’s much better and bigger pools of developers we should be trying to attract.

Ok, that’s enough for this rather rambling post.

Code Red

Code Ruby (or Python).


comments

Cheating at Computer Games   27 Jan 05
[ print link all ]
One of the most enjoyable programming exercises is writing a computer program that plays a game.

Paper Rock Scissors …

The weeks Ruby quiz involves writing a simple program to play the game "Paper, Rock, Scissors". Soon after the comments started on the quiz, someone submitted this 12 line "cheating" program:

  class Cheater < Player
    def initialize opponent
      Object.const_get(opponent).send :define_method, :choose do
        :paper
      end
    end

    def choose
      :scissors
    end
  end

It cheats in that it dynamically modifies its opponent object so that it always returns :paper, making it trivial to beat. While it does cheat, it does show some clever "thinking outside the box" approach to playing Paper, Rock, Scissors.

This reminds me of a story …

Greed

A number of years ago, I used to teach C++ in GE’s after hours education program. One of the projects assigned to the class was writing a player for the game of Greed. Greed is a simple dice game where you score points by rolling a set of 5 dice. You can continue to roll as long as you continue to make points on each roll. However, a row that has no point value causes you to lose all the points for a turn, so there is some value in stopping while you are ahead (hence the name Greed).

The game framework handles the dice rolling. All the student’s prgram had to do was decide whether or not to roll again given the the current state of the dice.

As part of the course, I pointed out that the encapsulation and data hiding features of C++ made it difficult to "cheat" by effecting the framework or other player objects. One particular student took this as a challenge and announced that he had a "cheating" player program that stayed within the "rules" setup by the C++ langauge, but yet would always win.

How did he do it? This cheating player would record the state of the seed of the standard library’s random number generator. It would then do trial rolls of the dice until it got a high scoring roll. Then it would reset the random number seed to the value that produced the high scoring roll, and indicate to the game framework that it wanted to roll again. The framework would comply and give the player the high scoring roll that it was setup to do. The cheating player would win the game in a single turn.

The cheat was easily defeated (once the cheat was understood) by using a private random number generator that wasn’t accessible to the player programs. But I learned two things from this exercise:

  1. Programmers love challenges, especially the kind that say "You can’t do this …"
  2. Secure software is hard to create, particularly because of point 1.


comments

Statically Typed Closures In Groovy   21 Jan 05
[ print link all ]

Closures in Groovy

There is an interesting debate going on about closures in the Groovy language. You can catch up on the discussion here:

For those who don’t want to read the whole conversation, I’ll summarize the bit I wish to address. Mike is advocating simplification of the Groovy language syntax to remove ambiguities and make have special cases. An admirable goal, no doubt.

In addition revoking the optional parenthesis and semi-colons currently sported by Groovy, Mike wants to make the closure syntax more explicit. Currently closures in Groovy look very similar to blocks in Ruby.

   list.each { item | println(item) }

Mike would like to see it changed to …

   list.each (clos(item) { println(item) });

Where clos is now a keyword that introduces the closure and the closure argument list is given explicitly in a way that is more Java-like. One of the reasons he likes this is that it opens the possibility of declaring the types of the arguments to the closure.

Quoting from the third link above:

[…] And for Groovy at least, all closures look alike - two closures which do radically different things and take different parameters can be used interchangably from a static typing perspective. Clashes are only detected at Runtime. By comparison an AIC is much more verbose in its definition, but it is statically typed, so you can’t use two AICs created against different interfaces interchangably. Dynamic typers will scoff at this, but in larger programs being able to define a definite type, parameters, and return type can be a huge boon. […]

Wow, a "huge boon". Let’s see how this boon works out in practice.

Static Closure Types

We will start this Mike’s suggested syntax add a return type declaration. This gives us the ability to declare closures like this:

  # A closure that takes an integer and returns an integer value.
  clos(int i):int { i + 1 }

  # A closure that takes a string and returns a integer.
  clos(String s):int { s.length() }

  # A closure that takes a string and returns no value
  clos(String s):void { println(s) }

I think you get the idea.

Note:
I’m using vaguely Groovy like syntax here, modified with a variation of Mike’s suggested closure syntax. I am not a Groovy expert and may get some details wrong. However, I don’t want to get hung up on the syntax here, but want to concentrate on the ideas behind them. So bear with me. Thanks.

For static typing to work, we need the ability to declare variables of a given closure type. Here we declare three variables that match the types of the three closures above.

  clos(int):int     integer_func;
  clos(String):int  string_func;
  clos(String):void handler;

Given the above declarations, we should be able to do the following:

  integer_func = clos(int i):int { i + 1 }
  string_func  = clos(String s):int { s.length() }
  handler      = clos(String s):void { println(s) }

In all cases, the type of the closure exactly matches the declared type of the variable. We will be able to invoke the closures and be certain that there will be no runtime type errors, which is certainly the goal of static typing.

Relaxing the Exact Match Rule

Consider the following …

  interface Animal { void talk(); }
  class Cat implements Animal { void talk() { println("Meow") }

Should the following assignment be allowed?

  clos(Cat):void cat_handler
  cat_handler = clos(Animal a):void { a.talk(); }

Let’s think about this. The closure can handle any type of Animal. The closure variable cat_handler is declared in such a way that only Cat objects can be passed to the closure referenced by cat_handler. Since Cats are Animals, this should cause no type errors at run time.

One more example, then we well summarize the rules.

  clos():Animal factory;
  factory = clos():Cat { new Cat() }

Again, this looks to be type safe. Closures called through the factory variable are guaranteed to return Animals. The closure in question returns a Cat object, which is certainly an Animal. This will cause no type errors at run time.

Summarizing the Closure Compatibility Rules

We can summarize the rules for assigning closures to closure variables as follows:

A closure can be assigned to a closure variable when:

  1. The closure and the closure variable take the same number of arguments, and
  2. the values passed in the argument list given to the closure variable can be assigned to the formal parameters of the closure, and
  3. the return type of the closure can be assigned to a variable of the return type of the closure variable.

The above language is way too informal for a real language definition, but I’m trying to get at understanding rather than exact semantics. The rules should do for our purposes.

A Real Life Example

We now have enough to try some real life examples. Consider the following transform function that takes a list and returns a new list built from an arbitrary transformation on each element. The transform is specified by a statically typed closure.

  List transform(List list, clos(Object):Object transformation) {
    List result = [];
    for (Object current_obj in list) {
      Object new_obj = transformation(current_obj);
      result.append(new_obj);
    }
    return result;
  }

Our closure is specified in terms of Object because we want it to work on any kind of list.

How would we use it? We might like to do the following:

  clos(int):int t = clos(int i):int { i + 1 };
  transform([1,2,3], t);

Oops! This doesn’t work. The closure t is not compatible with the closure needed by the transform function (it breaks rule 2 above). The closure t only takes arguments of type int, but transform could possibly pass any type to t.

Ok, it is pretty disappointing that the direct approach does not work. So let’s try something else. What if we tried this.

  clos(Object):int u = clos(Object obj):int { ((int)obj) + 1 };
  transform([1,2,3], u);

This works! But at what cost? Are you bothered that we needed a cast to get our closure to work properly? You probably should be, for what we have done is lie to the compiler, telling it our closure takes an arbitrary object when in fact it must have an integer (or something that can be converted to an integer).

If fact, we can now write code that is statically type valid, but fails at run time (with a class cast exception):

   u("hello")

If our statically typed closures can’t lead to code that is runtime type-safe, then what is the advantage of bothering with static declarations. We might as well stay with the dynamically typed closures that offer the same amount of type safety and are much less complex.

The Example Was Rigged!

Well, maybe. The problem lies in trying to use statically typed closures with functions that take generic arguments. But isn’t that exactly the situation where static type safety is most needed? If statically declared closures can’t handle the hard problems, why bother with them on the easy ones?

Can We Fix This?

Perhaps. I believe it is possible to define statically typed closures. In fact, this article lays out how to do it in Eiffel (the agents described in the article are essentially closures). But the Eiffel language supports generics (and in particular generic Tuples) to work around the problems outlined here. Adding that to the language makes it even more complicated.

Summary

At first glance, statically typed generics look really attractive, but add a great deal of complexity without achieving static type safety for a very common class of problems where closures are commonly used. I just don’t see the advantage.

Mike Spille says "On typing, it’s a double-edged sword if you’re coming from a Java perspective.", and he is right. And you really have to be careful not to cut yourself on that sword.



comments

Pennsylvania Dutch   20 Jan 05
[ print link all ]

Eckel on Ruby

Recently Bruce Eckel addresses the "Ruby Issue" yet again. Evidently a number of years ago Bruce brushed off Ruby based on a quick perusual of a Ruby book. Ever since then, the Ruby community has been trying to convert him, finding it hard to believe that someone as intelligent and well spoken as Bruce didn’t immediately fall in love with our little language.

In the article, he says:

Look at one of the first examples from "Why’s Poigniant Guide," where he’s asserting that Ruby is "the language of our thoughts":
  5.times { print "Odelay!" }

Or this:

  exit unless "restaurant".include? "aura"

This makes sense if you used to be a Smalltalk (or perhaps Forth) programmer, and I know one who started with Python and has moved to Ruby. It also makes sense if you grew up Pennsylvania Dutch, where they say things like "Throw Papa down the stairs his hat," and "Throw the horse over the fence some hay."

Well that explains everything! You see, my father was Amish (until he was about 13 years old) and my mother’s parents were Amish. The Amish speak Pennsylvania Dutch (a distant dialect of German) in their homes and use High German in their church services. Often Amish children will enter first grade before they learn to speak english.

So I grew up in a community where Pennsylvania Dutch was often spoken, and influenced the way english was spoken. This "backwards" way of speaking sounds very familiar to me.

No wonder I am so attracted to the Ruby language.

An Aside

I do find it humorous that Bruce is (mildly) poking fun at the Ruby (and Perl) technique of using if and unless as statement modifiers. Bruce makes it sound as if putting the if condition at the end of a statement (e.g. return if data.nil?) is somehow awkward english.

Now go reread the first two sentences of the paragraph that begins "This makes sense if …".

Interesting.

Coming Around?

Actually, from the tone of the article (and some of the later comments), I get the feeling that Bruce respects that fact that a lot of people find Ruby attractive and productive.

Its just that it doesn’t click for him.

That’s OK. As my Amish relatives would say, "It takes all kinds, the world to make".

Increase Your Ruby Skills

If you really want to increase your Ruby skills, I recommend that you spend a good part of the day listening to John Schmid sing a few Amish folk songs. My favorite is "Maydly vit du hayra?" where the Amish father queries his young daughter on the type of man she wants to marry. I actually remember the song "Reide, Reide, Geile" (Ride, Ride the Horse) sung in my house while growing up (usually as we were bouncing on Dad’s leg). Good Stuff!



comments

Web Applications   20 Jan 05
[ print link all ]
I am really suprised at the number of web applications that I have started using in just the past few months. I’ve never been a fan of web apps (too slow, clusmy interfaces, etc.), but GMail broke the mold here and showed the world what a good web interface could do. And I think we will see more of that as time goes on.

Anyways, here’s what I use…

BlogLines (bloglines.com)
After messing around with desktop news aggregators for a while, I moved to BlogLines to monitor all my news feeds. It solved two problems for me: (1) the need for a cross platform news aggregator and (2) keeping multiple workstations synced with regard to the news already read. This is the perfect fit for web app.
GMail (gmail.com)
I entered the GMail race late in the game, as I was very happy with my mail hosting on the UML Co-op system that I use. However I discovered one problem with hosting mail on the co-op box. When the box goes down, how do I send/receive mail on the UMLCOOP mailing list? So I broke down a got a GMail account mainly for backup. I’ve very impressed with the clean and responsive user interface. GMail has set the bar for all web apps in the future.
del.icio.us (del.icio.us)
Yes, that’s how its spelled, and yes, that is really the host name. Weird. But I love this site. I had heard about it for some time and never really understood what it was all about. Then two months ago someone demoed del.icio.us at our local Linux users group meeting. Since then I’ve got over 400 bookmarks added to the system.

So, what is del.icio.us? It is a bookmarking web app that allows you to add keywords to the bookmarks. For example, if I come across a great site about programming X10 devices in Ruby, I can bookmark the site and associate the keywords "ruby", "x10", "programming" with the bookmark. Later I can come back and ask for all bookmarks associated with "x10". Cool! Did you ever want to return to a site you had visited earlier, but just can’t remember where in web it was? Del.icio.us is a great answer to that.

And what’s more, your bookmarks are sharable. Today someone ask me about Ruby IDE’s and I sent them to my del.icio.us bookmarks: del.icio.us/jimweirich/ide+ruby

Ta-Da Lists (www.tadalist.com)
I just came across this one today, and its the real reason I started this blog entry. Wow, what a simple idea. And so beautifully executed. Notice the lack of submit buttons. Just start typing todo list entries, hitting a return to go to the next one. Finished a todo item and want to check it off? Just check the box … no submit button needed to get the changes back to the database. Ta-Da lists uses XMLHttpRequest to interact dynamically with the host. Beautiful.

And to top it off, it is a Ruby-on-Rails application. Written in 579 lines of Ruby code, that’s less than the size of the XML config files used in many J2EE applications. David is really showing off the latest features of Rails too.

Now, if I only liked todo lists …



comments

Ron Jeffries is Looking ...   20 Jan 05
[ print link all ]
It seems that Ron Jeffries is looking for a little help on his web site. One of the desirable skills listed is Ruby. He’s not promising to pay much, but hey, you get to use Ruby.

See www.xprogramming.com/Blog/Page.aspx?display=LittleHelp for details.


comments

XP Cincy Makes a Surprise Move   17 Jan 05
[ print link all ]
It is funny how surprises happen when you least expect them.

XP in Cincinnati

The Cincinnati XP Users group meets on the first tuesday of every month. At the meetings we "practice" our XP skills by pairing and working a small web project for Childrens Hospital. The project is a simple scheduler for reserving rooms.

Most of the meetings we have about 4 or 5 pairs of java programmers and one pair of Ruby programmers (the Ruby pair is usually me and Chris). Working on the project for only a few hours a month means that very little gets done at the meeting, but that’s ok because the meeting is more about learning the process that getting software delivered.

But working on the same project for over a year is getting a bit old, and the group was looking for something new to work on.

The January Meeting

I didn’t make the January meeting, so I only got this news second hand. Here is a portion of the meeting summary that Mark Windholtz published. As you read this, keep in mind that the group has been exposed to Ruby over the past year, but only a couple of them had actually paired with me and had direct exposure to the language.

So what’s next for the group? We talked about goals. do we want to …
  1. focus on delivery?
  2. focus on experimentation (with an eye toward adding buzz words to our resumes)?
  3. do something wacky and fun?
  4. something else …?

We kicked that around a little.

Bill suggested that they were not exclusive goals and said we could have fun while delivering.

Somehow we started talking about PHP and Rails. We watched a 10 min Rails video that took 12 minutes to download on the slow line. (you gotta see this!)

media.nextangle.com/rails/rails_setup.mov

If you have not seen the Ruby on Rails 10 minute demo movie, it is worth downloading and watching. Really, it is only 10 minutes (not counting download time).

Continuing with Mark’s summary.…

It blew us away how easy it was to build a minimal web app in 10 minutes.

After the video Bill announced that he had a Rails server installed and running.

Gerard commented that it took us 2-3 meetings to get Tomcat configured for peoples PCs, While it took Bill about 20 minutes to get a page showing in an unfamiliar technology.

Skipping over some discussion stuff …

So it turned out unanimous that we would load-up on Rails and next month start developing something. […] I think this is going to be huge amounts of fun.

I think its going to be fun too. I can’t wait until the next meeting. That’s a big part of the Ruby attraction. It puts the fun back in programming.

Invitation

If you are interested in Ruby, either a beginner or an expert, feel free to drop by the next Cincy-XP Users Group meeting on February 1. See www.objectwind.com/cgi-bin/wiki.cgi?MeetingTimeAndLocation if you need help finding us.



comments

Slowing Down Calculations   08 Dec 04
[ print link all ]
Normally you want your calculations to go as fast as possible. But not this time. Here we will look at how to slow them down. Slow them waaay down.

Time for Some Fun

If you are following the Ruby mailing list, you will see we just made a new release of RubyGems. For the past week, most of my spare time has gone into making that happen. Now its time to get back into some other projects that I’m woefully behind on …

But before we do, it’s time to have a little fun with Ruby.

A Language Based SpreadSheet

Patrick Logan wonders aloud why spread sheets haven’t made it into programming languages as first class objects. He then goes on to speculate what it might look like in Smalltalk. And along the way, he bumps into some interesting concepts about evaluating expressions.

Translating Patrick’s Smalltalk code into Ruby turns out to be quite straight forward …

  ss = SpreadSheet.new
  ss[1,1].value = 5
  ss[1,2].value = ss[1,1] * 6
  ss[1,3].value = ss[1,2] * 7
  puts ss[1,3].value             # => 210

ss is our spread sheet object. Indexing it with row and column indicies yields cells, which can hold formulas. The first cell gets a five. The second cell (at [1,,2]) gets 6 times that (30), and the final cell gets 7 times that (210).

Straight forward arithmetic, right?

Wrong!

Remember that this is a spread sheet. Changing the values in cells should effect cells that derive from it. In other words, if we change the value of ss[1,1] and then ask for the value of ss[1,3], we should get a new value.

  ss[1,1].value = 10
  puts ss[1,3].value     # Should now print 420!

The naive implementation of just storing values in arrays as we calculate them just isn’t going to hack it.

Ummm … Why Don’t We Use Lambdas?

My first thought was that we would want to just wrap the expressions in a lambda. Putting them in a lambda has the effect of deferring the evaluation of the expression until we really want it. And we can reevaluate the expression as needed as its dependents change.

The result would look like this …

  ss = SpreadSheet.new
  ss[1,1].value = 5
  ss[1,2].value = lambda { ss[1,1] * 6 }
  ss[1,3].value = lambda { ss[1,2] * 7 }
  puts ss[1,3].value             # => 210

In fact, a commenter to Patrick’s blog suggested the same thing.

Patrick responds.

The problem then is the programmer has to know where to put the blocks to delay computation and where to force the revaluations. Instead of blocks, though, my intention is to use some new object, a Formula, say, and to make those objects implicit as much as possible.

Deferring Expressions in Ruby

How would this work out in Ruby? In Ruby, all computation is accomplished by sending messages. To defer a computation, just capture its message and replay it later at your convenience. We will start with a formula object that turns any message sent to it into a deferred object.

  class Formula < Builder::BlankSlate
    def method_missing(sym, *args, &block)
      Deferred.new(self, sym, args, block)
    end
  end

Since you generally want every message sent to a formula object to be recorded, I based Formula on the BlankSlate class I’ve mentioned earlier. Deferred is also fairly straight forward to write. The initialize method just records the receiver of a message, the message name, arguments and any blocks.

  class Deferred < Formula
    def initialize(target, operation, args, block)
      @target = target
      @operation = operation
      @args = args
      @block = block
    end
    # ...

We derive Deferred from Formula because we want operations against a deferred object to also be deferred, and Formula handles that perfectly.

Now, when we want the value of a deferred operation, we need to ask it nicely. We will use a method named formula_value for that purpose.

    def formula_value
      @target.formula_value.send(@operation, *eval_args, &@block)
    end
    # ...

To replay the deferred operation, we need to get the target (reciever) of the message. Since the target was a formula, we need to ask for its formula value. We then send it the original operation (message name) and a list of arguments. Just one subtle note about the arguments. They too might (or might not) be formula objects, in which case we need to evaluate them before passing them to send. eval_args just creates a new list of formula values from the original argument list.

    def eval_args
      @args.collect { |a| a.formula_value }
    end
  end  # of class Deferred

Formulas in Action

Suppose I had a Formula object, and tried to add 1 to it. What would I get …

  result = some_formula + 1

result is a new deferred object, capturing the sending of +1 to the formula. To get the real value, we ask the result for its formula_value:

  result.formula_value

which recursively gets the formula value of some_formula and sends it a +1 message.

Something is Missing

Great. We see how to get deferred evaluations if we have a formula object, but where does that original formula object come from?

There are several options. One is to create a Formula wrapper around a normal Ruby value.

  class Const < Formula
    attr_reader :formula_value
    def initialize(value)
      @formula_value = value
    end
  end

Now we can say:

  result = Const.new(5) + 3    # Returns a deferred formula object

and later ask:

  result.formula_value         # Returns 8

Mirror, Mirror

Another question: I can see how formula+1 works. The + message is sent to a formula object and gets handled specially. But what if we write:

  1 + formula

Won’t 1 get confused because it doesn’t know how to handle a formula object?

Well, yes and no. The + operation in Integer will get confused, but it has a backup procedure to follow when it doesn’t know how to add itself to an arbitrary object: It asks the object how to do it.

Integer + will call coerce on the Formula object and expect Formula to figure out how to do the addition. Coerce should return a two element list whose elements can be added together.

Here is coerce for Formula:

  class Formula
    def coerce(other)
      [Const.new(other), self]
    end
  end

Formula just wraps the original number in a Formula wrapper and then lets the wrapper handle deferring the addition.

The SpreadSheet Cells

The Cells of the spreadsheet are Formula objects too. The only catch here is that they can hold different Formula objects over time. value should calculate the value of a cell (and will in turn call formula_value to do so). value=(val) should set the internal formula to whatever the user specifies.

Here is Cell in all its glory.

  class Cell < Formula
    def initialize
      @formula = 0
    end

    def value
      @formula.formula_value
    end

    def value=(new_value)
      @formula = new_value
    end

    def formula_value
      @formula.formula_value
    end
  end

Making a Cell object a formula is pretty key to the whole deferred calculation design. If a cell contains only normal Ruby objects, then its value can be calculated immediately. However, we want to defer calculations that depend upon other cells, and since adding a cell object to any expression will automatically defer it, we get the effect we want automatically.

Hey! Wait a Minute!

The astute observer will note that I initialize @formula in Cell to 0, but later in the code send a formula_value message to @formula. Won’t this cause problem, sending formula message to a non-formula object?

Well, yes it would. But it simplifies the code greatly if we just pretend all objects are formulas and can respond to formula_value. Non-formula objects just respond by returning themselves.

This is easy to accomplish.

  module Kernel
    def formula_value
      self
    end
  end

(And this is why I chose a rather awkward name for fetching a formula’s value. Since all objects will implement this method, I wanted it to avoid potential name conflicts).

The SpreadSheet

The final piece of the puzzle is the actual spread sheet object. Its only real function is to act as a lookup container for the Cell objects. My implementation uses a cheesy "convert two integers to a string key" technique. It works ok, but it bothers me.

  class SpreadSheet
    def initialize
      @hash = Hash.new
    end

    def [](r,c)
      @hash["#{r}@#{c}"] ||= Cell.new
    end
  end

That’s it. The guts of a spread sheet in under 100 lines of code. You can see the complete listing on my wiki.

Other possibilities

This technique of deferring calculations has some really interesting possibilities. Imagine that instead of replaying deferred calculations, we examined the deferred expression and performed some operation on them. Like what? Well, maybe optimizing the expression, maybe analyzing the variable references, maybe generating SQL from them. Remember the Criteria library … it uses this technique to translate ruby code into SQL conditions.

Limitations

Be careful with this technique. Although quite powerful, there are a couple of places that can cause problems. In particular, the && and || operators in Ruby are difficult to capture in this way.



comments

 

Formatted: 21-May-12 11:05
Feedback: jim@weirichhouse.org