nuthole.com "full of old bollocks since 2003"
contact:email
subscribe:feed
compute / programming
Powered by Blosxom
Get Firefox!
geourl
subgenius
spampoison hosting rails

Twitter? Twucket.

posted by jack at 11:11 CET in / compute / programming feed

Just in time for WWDC '08, allow me to introduce you to Twucket.

Twucket is a new Twitter client for Mac OS X that I am releasing as freeware. I created Twucket because I wasn't happy with any of the available ways to view my Twitter page. The actual twitter website is a hugely-rendered page, and requires you to go to the browser now and then to see if anything has come in; The standalone clients I tried were either wasteful of screen real estate, or didn't fit in with the overall Mac GUI, or contained ads.

Twucket suffers from none of those problems. Its interface is minimalist and small, it behaves like a normal Mac application, and it doesn't insert ads into the display. What it does offer is a compact, simple Twitter interface with a few features that Twitter users will probably appreciate. It uses a relatively small amount of screen real estate per message, so you can leave a small window open in a corner of your screen that is still large enough to show the latest 4 or 5 messages you've received.

I do have additional plans for future versions of Twucket, but today is the day for 1.0.0. If you're a Twitter user, please give it a try and let me know what you think!

permalink | digg this | slashdot this | add to del.icio.us |
Comments

Defective C++

posted by jack at 11:12 CET in / compute / programming feed

At my previous job, I worked as a C++ programmer. The job, which started out primarily working with Objective-C and Objective-C++ to port a Windows application to Mac OS X, became progressively more a C++ job as time went on. I came to learn C++ fairly well, just enough to realize how much I dislike it. I haven't properly been able to summarize just why I dislike C++ so much, but fortunately someone else has: The Defective C++ page, part of the larger "C++ FQA", lays bare a number of problems inherent to the design of the C++ language.

In less than two years of C++ usage, I encountered nearly every one of these problems. Some of them can be dealt with by deciding upon and following "best practices" within the team, which we did (to give credit where credit is due, the more experienced C++ developers I was working with pretty much laid out the best practices for the rest of us to follow), but some of them are just things that you pretty much have to live with, and end up turning the development process into a real straightjacket.

(via Another Day in the Code Mines, which had picked it up from a discussion at Joel On Software; check out the comments in that discussion if you want to see some extremely blindered C++ apologists in action...)

permalink | digg this | slashdot this | add to del.icio.us |
Comments

RoR etc

posted by jack at 13:02 CET in / compute / programming feed

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 |
Comments
Re: RoR etc
AngryMike wrote on Fri, 20 Apr 2007 08:19

Ruby! Now I have to make a RUBY Player! The Humanity of it all!


[ reply to this ]

huge volumes of pointless syntax

posted by jack at 14:22 CET in / compute / programming feed

It's high time for the next installment in my irregular series of C++ rants. This time I'm going to talk about expressiveness (or rather, C++'s lack thereof)

Expressiveness indicates a capability to accurately and briefly express something. A human being is considered to be expressive if they are able to speak in such a way that someone listening to them can quickly understand the person's message. A computer language's expressiveness is a measure of how quickly a programmer can write code that is unambiguous and a correct reflection of the design in the programmer's mind.

Let's illustrate this with an example. Imagine we've got a class with an instance variable containing an array (or list, or vector, or what have you) of some other type. We want to give our class a method that takes another array, iterates through all the objects in it, and adds them to its own array. Obviously this is a contrived example, since nearly all container classes in all languages must include functionality for adding the contents of one array to another, but the point here is to demonstrate how a simple iteration is done.

Let's start off with the C++ version, something like this:


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:

  • the syntactic indicator that we're dealing with an instance variable by always prepending self.
  • the more sensible method names in Python's array class compared to C++'s std::vector class (append vs push_back)
  • the 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 iteration
but the big win I want to point out here is really the difference in the for 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 |
Comments
Re: RoR etc
AngryMike wrote on Fri, 20 Apr 2007 08:19

Ruby! Now I have to make a RUBY Player! The Humanity of it all!


[ reply to this ]
Re: huge volumes of pointless syntax
AngryMike wrote on Thu, 15 Jun 2006 11:59

Preach it brotha!


[ reply to this ]
Re: huge volumes of pointless syntax
C++ Nazi wrote on Sun, 16 Jul 2006 12:23

Soon death will come to you...


[ reply to this ]
Re: huge volumes of pointless syntax
Ben Pope wrote on Tue, 29 Aug 2006 10:22

Actually, I would prefer the following in C++:

class ValueContainer {
    std::vector<Value> m_values;
    void addValues(const std::vector<Value>& values) {
        std::copy (values.begin(), values.end(), std::back_inserter(m_values));
    }
};

But that's just me.

Don't get me wrong, python has it's advantages, but when you write C++ like C++, and not like C, then you will realise that these simple things can be done just as well, if not better, in C++


[ reply to this ]
Re: huge volumes of pointless syntax
Jack wrote on Tue, 29 Aug 2006 12:18

Fair enough! However, I must point out again that this is a contrived example. The preferred method for python, in fact, would be something like this:

    def addValues(self, values):
        self.values.extend(values)

For any more complex task (doing more than just copying the values as in your code) requiring iteration, you end up needing the exceedingly convoluted iterator in C++.

I did recently see an article where B.S. himself proposed some new features for the next major revision of the C++ language, including the ability to eschew the nasty iterator declaration, effectively replacing this:

        for (std::vector<Value >::const_iterator it = values.begin(); it != values.end(); ++it)

with this:

        for (auto it = values.begin(); it != values.end(); ++it)

That's clearly an improvement, but... well, it's a bit like putting makeup on a pig. It *might* look marginally better, but underneath it's still not a creature you want to spend too much time with. ;)


[ reply to this ]
Re: huge volumes of pointless syntax
Ben Pope wrote on Tue, 29 Aug 2006 16:45

auto has been accepted into TR1, it's coming and it's genuinely useful in this situation.

There are lots of algorithms, adaptors and binders that make loops much easier, but often you end up needing to create a functor just to do some simple task. This is where things like boost::lambda come in, allowing you to create unnamed functions at the call site.

With the exressiveness of boost::lambda, I can think of many examples where large amounts of C++ code and loops can be reduced in size. This code starts to get a little easier to read though, until you're used to it.

The classic example, shamelessly stolen from: http://www.boost.org/doc/html/lambda.html#introduction

is: for_each(a.begin(), a.end(), std::cout << _1 << ' ');

Which doesn't involve any iterator nastiness at all (neither did my previous example).

By embracing the STL, sequences and templates, you can often get away from instantiating specific iterator types in your code.

Some people consider (classic) for loops in C++ almost as bad as goto :)


[ reply to this ]
Re: huge volumes of pointless syntax
Jack wrote on Wed, 30 Aug 2006 09:43

OK, I can see where that's going. I have great respect for the boost project. They have accomplished some amazing things considering what they have to work with (the C++ standard). This is the first time I've looked at the boost::lambda stuff and I have to say I'm pleasantly surprised at what they've enabled.

Yes, they're able to do proper lambda expressions, etc, but at the expense of some frightful syntax. It's not boost's fault, it just accentuates what a poor language lies at the core (quite ironic that C++, which adds such a *huge* amount of stuff to C, doesn't add minimal functionality that would make life easier for programmers in this millenium). We may scoff at things like object-oriented extensions to COBOL, but this is not far-removed. The fact that all these things are *possible* in C++ doesn't mean that C++ is a good language with which to use them, especially when there are plenty of decent languages that have such functionality built-in, and let you work with a syntax that doesn't make the Baby Jesus cry ;)


[ reply to this ]
Re: huge volumes of pointless syntax
Ben Pope wrote on Thu, 31 Aug 2006 08:35

Well I suppose that leads us to another nice library in boost, that will bring us full circle:

You can build your libraries in C++ (with all the advantages the lower level coding brings), and then wrap them with boost.python, enabling you to use a higher level language (guess which one!) to bring together the parts you need, creating applications quickly, but whilst minimising the overhead of a scripting language.

If you haven't seen the following article and are interested in both C++ and Python, then it is a must read: http://www.boost-consulting.com/writing/bpl.html


[ reply to this ]

The Horror of Compile-Time Binding

posted by jack at 20:16 CET in / compute / programming feed

I'm working on this C++ project, and suddenly realize how much we're using the visitor pattern. The point of the visitor pattern is to extract non-core functionality out of data-bearing classes, instead gathering that functionality into a visitor class that knows how to operate on all the data-bearing classes. You do this by making each "visitable" class implement a method that accepts a visitor object, which in turn calls a method in the visitor to do its work, along these lines:


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 B

And 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 A

Say 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 |
Comments
Re: RoR etc
AngryMike wrote on Fri, 20 Apr 2007 08:19

Ruby! Now I have to make a RUBY Player! The Humanity of it all!


[ reply to this ]
Re: huge volumes of pointless syntax
AngryMike wrote on Thu, 15 Jun 2006 11:59

Preach it brotha!


[ reply to this ]
Re: huge volumes of pointless syntax
C++ Nazi wrote on Sun, 16 Jul 2006 12:23

Soon death will come to you...


[ reply to this ]
Re: huge volumes of pointless syntax
Ben Pope wrote on Tue, 29 Aug 2006 10:22

Actually, I would prefer the following in C++:

class ValueContainer {
    std::vector<Value> m_values;
    void addValues(const std::vector<Value>& values) {
        std::copy (values.begin(), values.end(), std::back_inserter(m_values));
    }
};

But that's just me.

Don't get me wrong, python has it's advantages, but when you write C++ like C++, and not like C, then you will realise that these simple things can be done just as well, if not better, in C++


[ reply to this ]
Re: huge volumes of pointless syntax
Jack wrote on Tue, 29 Aug 2006 12:18

Fair enough! However, I must point out again that this is a contrived example. The preferred method for python, in fact, would be something like this:

    def addValues(self, values):
        self.values.extend(values)

For any more complex task (doing more than just copying the values as in your code) requiring iteration, you end up needing the exceedingly convoluted iterator in C++.

I did recently see an article where B.S. himself proposed some new features for the next major revision of the C++ language, including the ability to eschew the nasty iterator declaration, effectively replacing this:

        for (std::vector<Value >::const_iterator it = values.begin(); it != values.end(); ++it)

with this:

        for (auto it = values.begin(); it != values.end(); ++it)

That's clearly an improvement, but... well, it's a bit like putting makeup on a pig. It *might* look marginally better, but underneath it's still not a creature you want to spend too much time with. ;)


[ reply to this ]
Re: huge volumes of pointless syntax
Ben Pope wrote on Tue, 29 Aug 2006 16:45

auto has been accepted into TR1, it's coming and it's genuinely useful in this situation.

There are lots of algorithms, adaptors and binders that make loops much easier, but often you end up needing to create a functor just to do some simple task. This is where things like boost::lambda come in, allowing you to create unnamed functions at the call site.

With the exressiveness of boost::lambda, I can think of many examples where large amounts of C++ code and loops can be reduced in size. This code starts to get a little easier to read though, until you're used to it.

The classic example, shamelessly stolen from: http://www.boost.org/doc/html/lambda.html#introduction

is: for_each(a.begin(), a.end(), std::cout << _1 << ' ');

Which doesn't involve any iterator nastiness at all (neither did my previous example).

By embracing the STL, sequences and templates, you can often get away from instantiating specific iterator types in your code.

Some people consider (classic) for loops in C++ almost as bad as goto :)


[ reply to this ]
Re: huge volumes of pointless syntax
Jack wrote on Wed, 30 Aug 2006 09:43

OK, I can see where that's going. I have great respect for the boost project. They have accomplished some amazing things considering what they have to work with (the C++ standard). This is the first time I've looked at the boost::lambda stuff and I have to say I'm pleasantly surprised at what they've enabled.

Yes, they're able to do proper lambda expressions, etc, but at the expense of some frightful syntax. It's not boost's fault, it just accentuates what a poor language lies at the core (quite ironic that C++, which adds such a *huge* amount of stuff to C, doesn't add minimal functionality that would make life easier for programmers in this millenium). We may scoff at things like object-oriented extensions to COBOL, but this is not far-removed. The fact that all these things are *possible* in C++ doesn't mean that C++ is a good language with which to use them, especially when there are plenty of decent languages that have such functionality built-in, and let you work with a syntax that doesn't make the Baby Jesus cry ;)


[ reply to this ]
Re: huge volumes of pointless syntax
Ben Pope wrote on Thu, 31 Aug 2006 08:35

Well I suppose that leads us to another nice library in boost, that will bring us full circle:

You can build your libraries in C++ (with all the advantages the lower level coding brings), and then wrap them with boost.python, enabling you to use a higher level language (guess which one!) to bring together the parts you need, creating applications quickly, but whilst minimising the overhead of a scripting language.

If you haven't seen the following article and are interested in both C++ and Python, then it is a must read: http://www.boost-consulting.com/writing/bpl.html


[ reply to this ]
Re: The Horror of Compile-Time Binding
hidden to protect the guilty wrote on Wed, 19 Apr 2006 09:56

Hmm interesting,

Personally I think that you are confusing C++ in general with the C++ your C++-correctness-pattern-nazi friends are writing.

Unlearn what you have learned, follow your feelings etc etc :)


[ reply to this ]
 
Re: Re: The Horror of Compile-Time Binding
Jack wrote on Wed, 19 Apr 2006 12:00

Well you've got a point there. Of course, if my co-workers didn't have an interest in making use of patterns, they'd have done it the old-fashioned way: each visitor would instead be implemented with a massive switch/case or if/else construct to vary behavior according to an object's type (which you can get with RTTI). But that's not a very pretty solution either.

I think that deep down, every OO programmer has a gut feeling that object-oriented languages are "supposed to be" polymorphic, and that you really shouldn't have to query an object about it's type. When you then encounter a situation like I described above, and find that your choice of language has hobbled your ability to use polymorphism, the visitor pattern seems like a good solution. And in some situations I'm sure it is. But in some situations, like when many of the visitable objects don't have any meaningful actions in the visitors, you end up with lots of empty methods (or methods that call some default method that acts on a generic type). In the long run you can end up with lots of completely meaningless code.


[ reply to this ]
Re: The Horror of Compile-Time Binding
hidden to protect the guilty wrote on Wed, 19 Apr 2006 13:49

I think a decent architecture for your visitors would be something like:

ElementVisitor - doesnt care about element type and just has a visitElement function

TypedElementVisitor - derives from above, checks the type and calls the typed function

You derive from the first if you don't want to do type specific stuff and from the second if you do. Another thing that would make it better would be to have non-pure virtual functions for the typed callbacks - if you don't care about a certain type, just don't add the function.

Downside to this is things will go wrong when you add a new type and don't handle it in a visitor that should handle it. But there again you're a programmer not a child, you should be able to cope with it.

As for the language thing: different languages have different properties just like different cars do. Personally I prefer to be able to drive whatever car I need to rather than stick to just the specific make and model of my car.


[ reply to this ]
Re: The Horror of Compile-Time Binding
Peter Marklund wrote on Wed, 19 Apr 2006 18:10

Very interesting article Jack!

I don't quite have the time to verify this with a Java program right now but I think polymorphism would work for your example in Java. I can't remember having run into a situation where Java is not behaving polymorphically when it should.


[ reply to this ]
 
Java OK
Jack wrote on Thu, 20 Apr 2006 21:45

Right you are Peter, I just whipped up a java example and it works fine. This leaves C++ as the only mainstream OO language that suffers from this compile-time crippling.


[ reply to this ]
Re: The Horror of Compile-Time Binding
C++ NAZI wrote on Thu, 20 Apr 2006 11:03

BLASPHEMY! DEATH TO YOU ALL. >D


[ reply to this ]
The A, B example
Jonas Gauffin wrote on Fri, 04 May 2007 05:47

Well. That's why you are using pointers in vectors/lists =) Try changing to ptrs, and you'll see that the example works.

Use the shared_ptr if you do not to manage the pointers yourself.

---

I've switched to C#/Rails from C++/PHP5 =)


[ reply to this ]

Your Comment

 
Name:
URL/Email: [http://... or mailto:you@wherever] (optional)
Title: (optional)
Comment:
Save my Name and URL/Email for next time

Looking for programming talent that doesn't make you say "WTF!"? Try the hidden network.