That’s the way I’ve always felt about working with Ruby, at least until now. I’ve used Ruby off and on since 2001 but I’ve never known anyone else interested in it. Also, I’ve only used it for small side projects so I’ve never developed great fluency. All of sudden now, though, it seems like the whole damn world is interested and I’m starting to think I need to hone up my Ruby skills. This includes trying to actually code like a 'Real' Ruby coder.
So that was my mindset as I sat down last night to write a little chunk of Ruby code to process the comment file created by the PHP comment system I use in my blog. The file is called comments.txt and has a very simple format. Each line is a comment in the form:
ID | DATA | TIME | NAME | EMAIL | HOMEPAGE | CONTENT
My goal was to load this file into an array of hashes, so my first chunk of Ruby was super simple and looked like this:
def load_comments()
ra = Array.new
IO.foreach("comments.txt") do |line|
a = line.split('|')
h = Hash.new
h[:id] = a[0];
h[:date] = a[1];
h[:time] = a[2];
h[:name] = a[3];
h[:email] = a[4];
h[:web] = a[5];
h[:comment] = a[6];
ra.push h
end
ra
end
comments = load_comments()
This worked fine and if I had had a deadline or still wasn't concerned with style, I would have lived with this, but now a nagging little voice was creeping into my thoughts telling me there had to be a better way. If someone fluent in Ruby saw those seven lines mapping the array elements into a hash they would laugh. I knew, there had to be a Ruby way to deal with this type of repetition. So I put on my thinking cap and read some ruby code on the web and came up with this solution.
class Array
def to_h_for_keys(keys)
hash = {}
counter = 0
each do |value|
hash[keys[counter]] = value;
counter = counter + 1
end
hash
end
end
class String
def split_to_h(delim, keys)
split(delim).to_h_for_keys(keys)
end
end
def load_comments()
ra = Array.new
IO.foreach("comments.txt") do |line|
h = line.split_to_h('|', [:id, :date, :time, :name,:email, :web, :comment])
ra.push h
end
ra
end
comments = load_comments()
With this technique I extended the Array class by adding a method that created a hash from the contents of the array. The method took an array of keys that mapped to the receiving array's offset. At first I liked this solution, but it seemed like a lot of code to solve something fairly simple. In addition I just hated how I had that counter value in Array.to_h_for_keys. It just seemed wrong to mix iteration with indexed based access. So I started to think how I could solve that and I came up with the following.
class Iterators
def Iterators.each(c1, c2)
l1 = c1.length
l2 = c2.length
count = if l1 > l2 then l2 else l1 end
count.times() {|x| yield c1[x], c2[x]}
end
end
class Array
def to_h_for_keys(keys)
hash = {}
Iterators.each(self, keys) do |v1, v2|
hash[v2] = v1;
end
hash
end
end
def load_table(filename, seperator, columns)
a = []
IO.foreach(filename) do |line|
a << line.split(seperator).to_h_for_keys(columns)
end
a
end
comments = load_table('comments.txt', '|', [:id, :date, :time, :name,:email, :web, :comment])
Here I've built a parallel iterator and used that to implement to_h_for_keys(). I also made a few other changes. I got rid of String's split_h method, it didn't seem worth cluttering the String classes namespace with a method that wouldn't be that useful in the long run. I also pulled some of the constants of load_comments and turned it into a more generic load_table.
I liked this better from an engineering standpoint, but again, it was a lot of code. I might have kept this too, If I hadn't been reviewing the documentation for Array where I noticed an iterator method I had forgotten about: each_index(), that iterates a collection by indexes instead of values. I immediately realized I could have used this to implement my previous version without the ugliness of mixing the value iteration with indexed offset access. So as much as I liked my parallel iterator, I rewrote the code once again to look like the following:
class Array
def to_h_for_keys(keys)
hash = {}
each_index() do |x|
hash[keys[x]] = self[x];
end
hash
end
end
def load_table(filename, seperator, columns)
a = []
IO.foreach(filename) do |line|
a << line.split(seperator).to_h_for_keys(columns)
end
a
end
comments = load_table('comments.txt', '|', [:id, :date, :time, :name,:email, :web, :comment])
Now this is feeling better, but the to_h_for_keys() method is bugging me now. Much like the String.to_h method I expunged earlier, I now feel to_h_for_keys needs to go as well. So now my final version follows:
def load_table(filename, seperator, columns)
a = []
IO.foreach(filename) do |line|
parts = line.split(seperator)
hash = {}
parts.each_index() do |index|
hash[columns[index]] = parts[index]
end
a << hash;
end
return a
end
comments = load_table('comments.txt', '|', [:id, :date, :time, :name,:email, :web, :comment])
OK, so now I have 14 lines of code that does the same work as the original 18 but is more flexible and hopefully easier to maintain and reuses; I've also learned a thing or two on the way. So why did I tell this story?
There were two reasons. First, I wanted to document how screwy learning a programming language can be. Second, the whole experience is troubling me. I really just wanted to build something and I got diverted by the whole 'doing it right' thing.
The fact is we talk a lot about egoless programming when discussing team programming, but I'm thinking we should apply the idea to individual development as well. While it's good to strive to do things right, there's a line that can be crossed were you descend into creeping elegance. In this case the effort was probably worth it since I needed to learn a few things, but I know that wont always be the case; at some point the rewrites will be more out of ego than necessity. The problem is that just gets in the way of the real goal - building cool things.
[ no comments ]
Why was the boat stopping? It wasn't supposed to stop. I had only jumped into the water because the boat would soon pull me up again and now it was stopping. Why did I let them talk me into this? I don't even like water skiing. Crap, they're taking the engine cover off.
"Hey", I yell, hoping there's a serious problem and I wouldn't need to follow through with this.
"It will just be a minute", the call comes back.
I look down and see my legs and the water skis dangling above the darkness below. How deep is this really, I wonder; 15 feet, maybe, over my head for sure. Suddenly the image of shark looming up from the murky darkness flashes through my mind.
Stop that, I scold myself as I mentally measure the distance to the boat and the nearest shore. I'm floating just about in the center of the cove, but the boat is only 50 feet away. I can see Billy and Richie poking and prodding the engine and I can hear them laughing and talking but I can’t make out the words.
"Glad you’re having fun", I yell again.
I start to regret that we smoked that joint. They were obviously enjoying it, but I’m just feeling paranoid.
The shark thoughts well up again. Damn, damn, damn! I splash some water on my face to break the negative train of thought. There’s probably not a shark within a mile of here, I tell myself, at least none bigger than a couple feet long. Even if there was they don’t generally just up and bite you, they sniff you over on few times, bump into you, see what you’re made of, before they attack.
Intellectually I know there's little to worry about but I can’t shake the deep seeded fear. Heck, if there’s anything to worry about, it’s probably a school of Blue fish. They could really rip a person apart. I again start to stare into the water below me looking for tell tales flashes of silver in the dark. I’m so tense; I’m practically scrunched in ball.
Just stop thinking and enjoy this, I tell myself
It's a beautiful day; the water is warm and salty. I can still taste the flowery marijuana we smoked earlier. Lots of pot tastes bad, but this was really nice. There are a couple of pretty girls in bikinis back at the dock too, think about them.
I start to feel safe; the buoyant tugging of the floatation belt I’m wearing feels nice as it lifts my shoulders out of the water and into the warm sun. Across the harbor I see two other water skiers crisscrossing. The browns and oranges of the rocky coast are practically glowing in the bright noon sun. I wish I could paint that, I think.
BUMP.
I didn't imagine that; something just hit me on my bottom. Holy shit! Shit! Shit! In a panic, I kick off the water skis and start swimming as fast as I can to the boat. Just swim, just swim I tell myself, waiting for another bump, or worse a bite.
I cover the distance to the boat in wild flurry of splashes and kicks. Then just as I grab onto the boat and pull myself out of the water, it dawns on me; when I relaxed, the tail of a water ski floated up and smacked me on the rear. There was no blue fish; there was no shark.
I'm on my belly now, half out of the water, laying on the gunnel of the boat like a trained seal. The look on my friends’ faces is a mix of concern and surprise.
"What the hell happened?" they ask.
I know I will never live this one down, so I try and make the best of it.
I look them in the eye and allow a sheepish grin to cross my face and sum it up as best I can,
"Giant Salmon."
[ 3 comments ]
Pedantry: it's just how things work in the Python world. The status quo is always correct by definition. If you don't like something, you are incorrect. If you want to suggest a change, put in a PEP, Python's equivalent of Java's equally glacial JSR process. The Python FAQ goes to great lengths to rationalize a bunch of broken language features. They're obviously broken if they're frequently asked questions, but rather than 'fessing up and saying "we're planning on fixing this", they rationalize that the rest of the world just isn't thinking about the problem correctly. Every once in a while some broken feature is actually fixed (e.g. lexical scoping), and they say they changed it because people were "confused". Note that Python is never to blame.In contrast, Matz is possibly Ruby's harshest critic; his presentation "How Ruby Sucks" exposes so many problems with his language that it made my blood run a bit cold. But let's face it: all languages have problems. I much prefer the Ruby crowd's honesty to Python's blaming, hedging and overt rationalization.
I'm not involved enough with the Python world to know if the first paragraph is really accurate, but from my pokes and prods from the perimeter it seems true enough. What really got me thinking however was the second paragraph about Matz and Ruby. The whole demeanor of the Ruby crowd really is a lot more laid back and plain speaking. (Note to self: this probably partially explains why I like it). As a futher example of like minded thinking check out the post Dividing with 'such careless honesty' by David H Hansson on the reaction on Slashdot to the Rails tag line: "...it works mostly right, most of the time, for most of the people". If you don't want to read it all here's the summary bits.
Naturally, I don't just like this statement for the outrage it caused. I like it because it's a dividing issue. One which can garner as much love and appreciation as it can cause outrage and despair. It exposed a set of deep cultural lines that in my mind separated the wheat from the chaff.So if that statement rubbed you the wrong way, it's an early warning signal that Ruby on Rails wouldn't be for you. It's kinda like applying the principle of "fail fast" to tech stack selection. There's no reason to waste your time, or that of the Rails community, by investigating further, if this level of truthfulness is rejected in your bones.
It would be easy to misunderstand the above as a religious argument - you must have faith in Ruby, Brother - but that would miss the real more human point and the great irony in all of this. The Ruby enthusiast isn't preaching programming religion, they're simply excited about being productive and the creative potential of the platform's tools, warts and all.
[ no comments ]
[ 1 comment ]
[ no comments ]
One of my first posts was about the Caganer, the Catalan Christmas figurine taking a poo (See Wikipedia). Given the European opinion of President Bush, this years new model isn't really a surprise. It would probably sell well in nearly 50% of this country as well.Update: If you follow the Wikipedia link above, make sure you check out the Tio de Nadal Christmas tradition. Tio de Nadal is a tradition where kids care for a log for a few days and then on Christmas everyone beats the log with stick to encourage it to poops treats. I love old school, pagan traditions like this. It actually sounds fun.
[ no comments ]
I love this part that demonstrates the message based behavior of Ruby.
class VCR
def initialize
@messages = []
end
def method_missing(method, *args, &block)
@messages << [method, args, block]
end
def play_back_to(obj)
@messages.each do |method, args, block|
obj.send(method, *args, &block)
end
end
end
vcr = VCR.new
vcr.sub!(/Java/) { "Ruby" }
vcr.upcase!
vcr[11,5] = "Universe"
vcr << "!"
string = "Hello Java World"
puts string
vcr.play_back_to(string)
puts string
This prints out:
Hello Java World HELLO RUBY Universe!
This is like nuke powered dynamic proxies. My brain swirls just thinking of the possibilities.
[ 1 comment ]
[ 4 comments ]
Approaching harbor is a bad time for a helmsman to fall asleep, or an officer of the watch to be in the head, or drunk, or whatever. In fact, here's what can happen:
A vessel was due to arrive at a port in Spain at 0800 local time (LT). It would appear that at about 0600 LT the vessel contacted the Pilot Station confirming the ETA and was instructed to contact again some 20 minutes before arrival.
At 07.59 hours LT, and despite the calls from the Traffic Control, the vessel grounded at full speed on the breakwater at the entrance to the port.
A video, taken by surveillance cameras, shows "live" the sequence of the grounding, and needs no comments.
Watch it--and cringe....
(video link)
[ no comments ]
I know simplicity sells to some degree at the consumer level. My old employer, Alpha Software built itself on developing approachable products. You might think that the fact that most of you know of a product called 'Microsoft Access' but not of our product called 'Alpha Five' invalidates this thesis, but I don't think so. How many small companies, that went head to head with MS in the 80's and 90's, are still around to talk about it? Not many. But Alpha is and that's because they made developers, who were willing to risk a less traveled path, more productive.
This whole post came about because of my continuing work with Ruby on Rails. Once the hype wears off from it and people understand its capabilities and limitations will they see that its inherent simplicity trumps its less stellar attributes? The fact is I don't know how it performs head to head against other similar products in terms of raw performance, I'm just guessing that it's slower given its foundation. The fact is I just don't think that matters.
In the not so distant future I see a world were reasonable performance is a given and the talk is more about simplicity and maintainability. In many ways Java started down this road as well, but got hijacked by the J2EE chowder heads. Perhaps it was just too early on the curve and not quite simple enough even by itself. I for one look forward to the new world. It's going to be fun.
[ no comments ]
[ 1 comment ]
Approaching harbor is a bad time for a helmsman to fall asleep, or an officer of the watch to be in the head, or drunk, or whatever. In fact, here's what can happen:(video link)A vessel was due to arrive at a port in Spain at 0800 local time (LT). It would appear that at about 0600 LT the vessel contacted the Pilot Station confirming the ETA and was instructed to contact again some 20 minutes before arrival.
At 07.59 hours LT, and despite the calls from the Traffic Control, the vessel grounded at full speed on the breakwater at the entrance to the port.
A video, taken by surveillance cameras, shows "live" the sequence of the grounding, and needs no comments.
Watch it--and cringe....
[ 2 comments ]
Kubi Software (Lincoln, Massachusetts). The basic premise of Kubi Software, and one that I agree with, is that while we would like all of the notes and communications associated with every sales opportunity to be kept in our CRM systems, the reality is they are not. Instead, they are far more often found in emails. For example, when a rep answers a prospect's question, negotiates with finance for a better price or strategizes with management on how to win the deal, he or she is more likely to store all that information in email, not the CRM system. Designed to be an extension of your existing CRM framework, Kubi organizes all those messages, priorities follow-ups and creates an audit trail of who said what to whom during the sell cycle.
FYI: With the departure of Ned there will be an opening in development for a top-notch senior engineer. I'll post a link to the job description when I have it. (Updated: 12/14/05 at 1:00pm) Here's the job posting
Senior or Principal Software Engineer – BostonIf you're interested contact me at plyons AT kubisoftware.comYou will be part of a small team of very experienced engineers creating a new generation of sales effectiveness and business process automation software that uniquely bridges Email metaphors with structured business processes. The perfect candidate will be hard-working, self-driven and highly motivated and thrive in producing high quality, customer-focused software while continuously seeking to strike the right balance between agile software development methodologies and the time, quality and customer-driven pressures of a dynamic, rapid start-up company.
Qualifications required:
8+ years of architecting, designing and developing commercially successful software products
Proven problem solving
Strong C/C++ programming skills and object oriented programming
Multi threaded programming/SMP
Strong relational database background (SQL Lite, Postgres)
.NET, COM, ATL, WIN32, SQL
Web Services, XML, SOAP, XSLT, CSS
Multi and cross-platform development skills in a client-server environment (Windows, Linux and Unix)
Excellent verbal/written communication and interpersonal skills
Bachelor’s degree or equivalent Qualifications desired:
C#
Java
Tools knowledge and languages such as Perl/Python/Ruby
Client, Server, Offline Networks, Replication, Collaboration
[ no comments ]
public static Size JustMeasureTheDamnString(Graphics graphics, string str, Font font)
{
CharacterRange[] characterRanges = {new CharacterRange(0, str.Length)};
SizeF sizef = graphics.MeasureString(str, font);
RectangleF layoutRect = new RectangleF(0, 0, sizef.Width, sizef.Height);
StringFormat stringFormat = new StringFormat();
stringFormat.FormatFlags = StringFormatFlags.NoClip |
StringFormatFlags.MeasureTrailingSpaces;
stringFormat.SetMeasurableCharacterRanges(characterRanges);
Region[] stringRegions;
stringRegions = graphics.MeasureCharacterRanges(str, font, layoutRect,
stringFormat);
if (regions.Length == 0)
{
return Size.Empty;
}
return regions[0].GetBounds(g).Size.ToSize().
}
[ no comments ]
[ 3 comments ]
class modifies Obj {
directive access( ... ) {
array a = arguments();
string code = "class modifies ${Class.name(self)} {";
Array.each( a ) using ( name ) {
if( name isa string ) {
code += "
function set$name( void value ) {
.$name = value;
}
function get$name() {
return .$name;
}";
}
};
code += "}";
eval( code );
}
}
class Example {
private number X = 10;
private string foo = "hi";
[access X, foo];
}
[ 1 comment ]
Ingredients
1 Kielbasa Sausage
1 Chorizo Sausage
1 Onion
1 2lb. bag of uncooked frozen shrimp
1 Shaker of Cajun spice such as Emeril's Essence
1 Can/bottle beer
Equipment
1 Tin foil pan
1 Knife
1 Tongs or a spoon
1 Grill
I never have time to do any preparation before hand so this recipe is tailored to on site preparation. We actually stopped at the grocery store on the way to the game. You can do all the chopping and peeling ahead of time if you want.
Preparation
Start the grill. Chop the Kielbasa and Chorizo into 1 inch chunks (finger food size) and put into the tinfoil pan. Chop the onion into chunks and add. Put the tinfoil pan on the grill and spinkle in a heavy dose of Cajun seasoning. Let the sausage mixture cook until the onions are translucent and the sausage is hot. Now add a beer to the tinfoil pan. When the beer starts to boil stir in the shrimp to the mixture. Sprinkle more Cajun seasoning. Close the grill lid (or add tinfoil cover) and cook until the shrimp are done.
Serving
Remove the tinfoil pan from the grill and place and place in a central location. Ring the dinner bell. Note, I didn't peel the shrimp but you can certainly do that if you desire. I figured since we are eating with our fingers it's easy enough to just let everyone dig in and peel their own.
[ no comments ]
[ 2 comments ]
[ no comments ]

