|
nuthole.com
"steadily enriched with an almost fabulous daring and originality"
contact: subscribe: follow me on twitter: compute / programming |
|
|
2008 06 09 - Mon Twitter? Twucket.posted by jack at 11:11 CET in / compute / programming
|
| permalink | | digg this | slashdot this | add to del.icio.us | | 0 comments (why don't you write the first?) |
| permalink | | digg this | slashdot this | add to del.icio.us | | 0 comments (why don't you write the first?) |
OK, i haven't posted in ages, but what can I say, life is keeping me busy. Recovering from a bit of minor surgery, followed by a long Easter break, followed by stomach flu, consumed the past few weeks.
But, more importantly than all that, I changed jobs at the end of January, and am now spending most of my work time (not to mention free time) programming with Ruby on Rails!
Ah, ruby. I've used python for a variety of side projects over the past few years, and I've long held the opinion that the two languages are largely equal, but in the short time since I started using ruby full-time, I'll admit that I've started acquiring a definite ruby bias. Things like ruby's block syntax, for instance, don't really have a corresponding language feature in python (at least, not anything quite as succinct).
Above all though, I'm mainly happy to be back in the world of dynamic languages. At my last job, I spent two years using C++, which taught me a lot: mainly that I don't like C++. In fact, I've started to get the feeling that using C++ at all, when other options are available, is in many cases a textbook example of premature optimization. You end up consuming lots more programmer-hours (which means greater expense, and later releases) chasing the chimera of an end-product which "runs faster" than a similar program written in a dynamic language would run. I put "runs faster" in quotes because in reality, most software is I/O-bound most of the time (either waiting for user input, or waiting for disk or network traffic), so the amount of time that your particular program is actually running is probably pretty small (and getting smaller as CPU speeds increase), no matter what language it's written in.
| permalink | | digg this | slashdot this | add to del.icio.us | | 1 comment (add more!) |
class ValueContainer
{
// a vector containing Value objects
std::vector<boost::shared_ptr<Value> > m_values;
void addValues(std::vector<boost::shared_ptr<Value> >values)
{
// an iterator pointing at the first value
std::vector<boost::shared_ptr<Value> >::const_iterator it = values.begin();
// an iterator pointing at the special "end" value indicating we've reached the end
std::vector<boost::shared_ptr<Value> >::const_iterator endIt = values.end();
// step through the entire array
for (; it != endIt; ++it)
{
// add the value to our array
m_values.push_back(*it);
}
}
};
Some of you may be unfamiliar with the boost::shared_ptr stuff in there. The boost project is a sort of proving ground for classes that are probably destined for the standard c++ library in the future, and shared pointers are a very valuable concept that make c++ development a little more do-able, by helping you manage object ownership.
Anyway, that's quite a pile of code, isn't it? All we're doing is walking through a list of things!
Some may balk at this, saying that the boost shared pointers are overkill here. OK, fine, let's take those away, and take away the comments, and stuff all we can into the for, getting this down as tight as we can:
class ValueContainer
{
std::vector<Value> m_values;
void addValues(std::vector<Value> values)
{
for (std::vector<Value >::const_iterator it = values.begin(); it != values.end(); ++it)
{
m_values.push_back(*it);
}
}
};
Now let's see how a similar class might look in python:
class ValueContainer:
def __init__(self):
self.values = []
def addValues(self, values):
for v in values:
self.values.append(v)
Unlike C++, Python has no free-standing declarations of instance variables--they're defined only as they're referenced inside of methods, and the best practice is typically to provide an initial definition inside the initilializer method __init__ as I've done here.
There are several nice things about Python that this highlights:
self.append vs push_back)values parameter isn't statically typed, so it doesn't need to actually be a standard python array, but can be any class that implements the methods required for iterationfor constructs.
Look at the mass of ugliness in the C++ for construct. To start off with, you're forced to declare a variable with a bizarre iterator type. Why should I have to care about that? I'm not using it in any way; that type is completely irrelevant to my code. Later, to iterate I have to use it++, which may not be pointer arithmetic, but at least looks like pointer arithmetic, and that is just so 1985.
Then, take a look at the simplicity of the Python for construct. The designers of Python built iteration right into the language! You simply give for the name of the variable that should be used to hold a value from the array, and the array itself, and off it goes. Behind the scenes, iteration occurs through well-defined methods, and if you're making your own collection class you can define your own iteration scheme, but all of that is completely invisible to a user of the class.
IMHO, this sort of thing is really one of the biggest wins for languages like Python. You get to leave out all sorts of meaningless busy-work, since the language includes useful mechanisms that C-based languages don't, and you are left with the job of programming the actual functionality you're after.
| permalink | | digg this | slashdot this | add to del.icio.us | | 7 comments |
void MyClass::accept(Visitor &v)
{
v.visitMyClass(this);
}
That way, new visitors can be created to implement new functionality involving visitable classes, without the visitable classes needing to be changed or even recompiled. Here's where the fun kicks in. For each new "visitable" class I add, I need to add a member function for each existing visitor, which means editing dozens of .h and .cpp files, adding a small "visitXXX" member function to each.
The kicker is that most of the functionality we're implementing with visitors consists of either trivial one-liners or empty implementations for most of the visitable types. So we have visitor classes containing dozens of identical member functions. Nothing quite like the joy of editing dozens of files to insert meaningless boilerplate code!
At first I wondered what my co-workers were thinking when they set out on this path, but eventually I came to realize, much to my chagrin, that this is an inescapable reality of C++ programming in many situtations, thanks to the compile-time binding that C++ does. The compile-time binding actually precludes you from doing certain things without this sort of indirection.
To see why this is, first consider an example that demonstrates how things can work in a more full-featured OO language, such as python. Consider the following python program:
class A:
def emit(self):
print "Class A"
class B (A):
def emit(self):
print "Class B"
a = A()
b = B()
a.emit()
b.emit()
print "-----"
objects = [a, b]
objects[0].emit()
objects[1].emit()
This code snippet defines and manipulates a simple class hierarchy, with B inheriting from A. Common sense tells you what the output of this program may be:
Class A Class B ----- Class A Class BAnd your common sense would be right. Whe calling the emit method on the a and b objects, the correct method is triggered and the corresponding class names are output. Now then, considering the following C++ program:
#include <iostream>
class A
{
public:
virtual void emit() {
std::cout << "Class A\n";
}
};
class B : public A
{
public:
virtual void emit() {
std::cout << "Class B\n";
}
};
int main (int argc, char * const argv[]) {
A a;
B b;
a.emit();
b.emit();
std::cout << "-----\n";
A objects[] = {a, b};
objects[0].emit();
objects[1].emit();
return 0;
}
Does your common sense tell you this program should have the same output as the earlier python version? Think again:
Class A Class B ----- Class A Class ASay what? "But, but, but, I put an instance of B in that array, wtf?" Well, here's where C++'s compile-time binding came and shot you in the foot. Note that C++ doesn't offer any sort of generic array of objects. Whether using a C array as I've done, or a std::vector object, you must indicate the type of the object, in this case the root of our hierarchy, A. C++ then makes use of this type information when your program is compiled to match up the call to emit with the implementation in the named class, rather than the implementation in the derived class you're actually instantiating! Doh! This has dire consequences for any situation where you've got a collection of objects of varying types. Say you've got a bunch of model classes, and you'd like each class to emit an xml string containing its values. In Objective-C, Python, Ruby, Smalltalk, and probably even Perl and Javascript, this is pretty straightforward; You just give each class a method that emits a string (and, assuming you've got an acyclic directed graph, calls the emitting method in each of its "child" objects). Then you just call the emitting method in the root object, and away you go. For example, once again consider some python:
class RootElement:
def emitXml(self):
print "<root>"
for child in self.children:
child.emitXml()
print "</root>"
class SomethingElement:
def emitXml(self):
print '<something value="', self.value, '"/>'
class SomeoneElement:
def emitXml(self):
print '<someone value="', self.value, '"/>'
r = RootElement()
c1 = SomethingElement()
c2 = SomeoneElement()
c1.value = "first child"
c2.value = "second child"
r.children = [c1, c2]
r.emitXml()
(Note that this code breaks encapsulation quite a bit in setting attributes on the objects, and is not what I'd do in production code. But python lets you do it, and it makes examples short and sweet, so there you have it.)
Now then, that code outputs about what you might expect:
<root> <something value=" first child "/> <someone value=" second child "/> </root>Simple, right? Well, if you try to do something similar in C++ (or Java for that matter), you'll run into trouble. You can start by giving your element classes a common parent declaring emitXml (don't need to bother with such a thing in python, where method calls aren't bound to implementations until runtime), but as soon as you have something like RootElement maintaining a list of child elements, you'll hit the same snag in our earlier example; the relevant implementations of emitXml will be passed over in favor of the generic implementation in the parent class. Which is where the visitor pattern comes in. It's basically a kludge to work around the compile-time binding limitation of C++. Even when there are times you'd want to use it in order to extract responsibilities out of a data-bearing class, you still end up with this ridiculous double-dispatch syntax (compare with Objective-C, where you can actually add methods to an existing class by using a category, so you can accomplish the same design feat of grouping functionally-related methods together, yet outside the classes they operate on, without the artifices of the visitor pattern). The moral of this story is that the visitor pattern is a necessary evil for statically-bound languages like C++. And that if you want to avoid this evil thing, start by avoiding C++. Really, pick up python or ruby or Objective-C or whatever. Really! [update] fixed some typos, thanks weinholt
| permalink | | digg this | slashdot this | add to del.icio.us | | 7 comments |