Web Development

My first IronRuby application

Justin James describes writing a Ruby application from scratch in order to solve a problem TechRepublic writer Chad Perrin was having. He also shares his first impressions of IronRuby.

In my continuing exploration of IronRuby, I was in search of a good opportunity to try writing a Ruby application from scratch that was interesting but not trivial. Fortunately, TechRepublic writer Chad Perrin posted a blog that fit the bill. To summarize his post, he was looking for a good way to find out for a roll of multiple dice of the same size, how many combinations add up to each possible sum. For example, if you have five six-sided dice, what is the number of permutations that add up to the numbers five through 30? I decided that working on his problem would be the perfect opportunity for me to really explore IronRuby.

Yes, there are some mathematical approaches to this problem that eliminate the need for super fancy programming. But I'm not a math whiz, and implementing a three-line formula wasn't going to help me learn Ruby or use the IronRuby environment. So I set about solving the problem myself, from scratch.

My first attempt at solving this was a bit too clever by half: I tried to construct a string of code using loops and then run it through eval(). This is the kind of thing I used to do in Perl all the time. While this approach has merit, it felt like it was too much of a workaround. The final nail in the coffin for me was that I don't know Ruby well enough to be able to write code that writes code and have the written code actually work. Debugging eval()'ed stuff can be a nightmare, in my experience. After about 30 minutes of frustration, I took a step back.

After writing to Chad about the problems I was having, I realized that I would have been better served by using a recursive function to write my code to eval(). The major challenge with this problem is that, while it can be solved with nested loops, the number of levels of nesting is unknown at the time of writing; this is what I was hoping to mitigate with my eval() approach. As I sat down to write the recursive version of the code generator, a lightbulb went off in my head: "if I'm writing a recursive function, why not just solve it recursively?" So I did, and less than 30 minutes later (remember, I never wrote Ruby from scratch before), I had a working application. Now, the code isn't perfect. At the time of this writing, it isn't creating nice output, and it isn't calculating the percentages. These issues are easily solved. But for my first try at this problem, I am proud of the output. See the code sample below.

def calculate (iteration, low, high, currentsum, output)
if (iteration == 1)
low.upto(high) do |value|
newsum = currentsum + value
output[newsum] += 1
end
else
low.upto(high) do |value|
calculate(iteration - 1, low, high, currentsum + value, output)
end
end
return output
end
diceInput = ARGV[0].to_i
lowInput = ARGV[1].to_i
highInput = ARGV[2].to_i
if (diceInput < 1)
puts "You must use at least one dice."
return
end
initResults = Hash.new
(lowInput * diceInput).upto(highInput * diceInput) do |value|
initResults[value] = 0
end
results = calculate(diceInput, lowInput, highInput, 0, initResults)
results.each do |result|
puts "#{result}"
end
puts "Press Enter to quit..."
gets

My thoughts about IronRuby

While working on this solution, I got more familiar with IronRuby. To be frank, it needs some work in terms of its integration with the Visual Studio IDE. As a Ruby interpreter, it seems fine (I know it doesn't get 100% on the Ruby compatibility tests), but the integration isn't what I need. For one thing, the "Quick Watches" do not work at all from what I can tell. Setting watches does not seem to work either. You can do value inspection via the "Locals" window, though. But it's really unpleasant to see the options you really want but not to be able to use them. The lack of IntelliSense isn't a deal breaker, but it sure would be nice. No F1 help is pretty harsh, especially for someone like me who is not familiar with Ruby at all. It felt very old-school to be thumbing through my copy of The Ruby Programming Language while working!

I also found it rather interesting how Ruby handles variable typing. I'm so used to Perl, where a variable's type is essentially determined by usage on a per-expression basis. For example, you can assign a string literal that is composed only of numbers to a variable, and then perform mathematical operations on it. In Ruby, this doesn't happen. Instead, if I assign a string literal to a variable, it functions as a string until I assign something of a different type to that variable. While this is perfectly sensible, it went against the grain of my way of thinking. Once I got a handle on this, my work in Ruby went a lot more smoothly.

Summary

I'm certainly no Ruby expert, but at this stage in the game, I feel like it is a language that I want to continue using in my career. Ruby has a lot to offer in terms of expressiveness. Soon, I will explore its use in Windows Phone 7 applications and take a look at how it interoperates with the .NET Framework.

If you have any ideas for things that you want me to cover in terms of IronRuby, please let me know in the discussion.

J.Ja

About

Justin James is the Lead Architect for Conigent.

2 comments
Sterling chip Camden
Sterling chip Camden

... you're finally taking the Ruby plunge. Can't say that I'd find Visual Studio all that helpful, though. I use Ruby from the command line on all platforms quite happily. The new site really messed up your code formatting, so much so that I'm having trouble reading it. I guess I'll copy/paste and reformat in vim.

Sterling chip Camden
Sterling chip Camden

Just for s & g, let me see how code gets formatted in a comment with pre and code tags: def calculate (iteration, low, high, currentsum, output) if (iteration == 1) low.upto(high) do |value| newsum = currentsum + value output[newsum] += 1 end else low.upto(high) do |value| calculate(iteration - 1, low, high, currentsum + value, output) end end return output end diceInput = ARGV[0].to_i lowInput = ARGV[1].to_i highInput = ARGV[2].to_i if (diceInput < 1) puts "You must use at least one dice." return end initResults = Hash.new (lowInput * diceInput).upto(highInput * diceInput) do |value| initResults[value] = 0 end results = calculate(diceInput, lowInput, highInput, 0, initResults) results.each do |result| puts "#{result}" end ~

Editor's Picks