Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

Be grateful for Perl OO

by dragonchild (Archbishop)
on Jan 28, 2003 at 15:14 UTC ( [id://230610]=perlmeditation: print w/replies, xml ) Need Help??

In my current position, I work with both Perl and C++. Personally, I prefer working in Perl, but see a very important place for C++ in a number of applications. (Far less than it's used for, but I'm biased towards more intelligent and less insular languages ... like Perl!)

Recently, I found myself working with a C++ file. What it does is irrelevant. What's important is the following pseudo-code:

for (int i=0; i < SomeValue; i++) { Class_A *a; Class_B *b; int SomeID; if (SomeFlag != SomeValue) { a = (Class_A *) some_param; SomeID = a->getID; } else { b = (Class_B *) some_param; SomeID = b->getID; } switch (SomeID) { case FIRST_CASE: if (SomeFlag != SomeValue) { SomeArray[i].value = a->FirstMethod; } else { SomeArray[i].value = b->FirstMethod; } break; case SECOND_CASE: if (SomeFlag != SomeValue) { SomeArray[i].value = a->SecondMethod; } else { SomeArray[i].value = b->SecondMethod; } break; } }
That switch has over 80 choices. Class_A and Class_B share an interface, but not a base class.

The be-grateful part here is that, because of weak typing and lazy evaluation, we could do the following in Perl:

my %CaseToMethod = ( FIRST_CASE => 'FirstMethod', SECOND_CASE => 'SecondMethod', ); for my $i (0 .. $SomeValue - 1) { my $method = $CaseToMethod($some_param->GetID); $SomeArray[$i]{value} = $some_param->$method; }
A few of the many benefits that scaling 80 * 8 lines down to 80 * 1 + 8 (and being able to separate the two):
  1. Easier to read because I can parse the concept
  2. Easier to maintain because it's easier to understand
  3. Easier to improve because I can see everything I'm touching
  4. Less likely for bugs to creep in because I don't have to keep 80 things in my head
  5. Just darn nicer to deal with!
I'm not attempting to slam C++. There are tons of reasons to work in C++ over Perl, the main one being that it's a technology choice that was made 4 years before you heard of the project. But, that doesn't mean I can't have a soft spot in my heart yearning for the greener pastures of Perl.

------
We are the carpenters and bricklayers of the Information Age.

Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

Replies are listed 'Best First'.
Re: Be grateful for Perl OO
by artist (Parson) on Jan 28, 2003 at 15:45 UTC
    1. Create your code in Perl to go with
    2. templates to
    3. create C++ code out of it.

    You get all the benefits of Perl that you mentioned and your final code is in C++. While maintaining the code, you can just look at the Perl code and re-create the C++ code whenever required.

    artist

      Ah, templating. A really handy technique many people forget about when writing C/C++ code, as we've been burned by the horror that is the C macro system. (Which, BTW, may well be of use here by itself)

      We're using a custom templating system pretty aggressively in parts of Parrot, I've used custom built templaters to preprocess a chunk of source before cut'n'pasting it into another source file somewhere--while it's not the most elegant solution, and you usually only do it once, it takes a lot of the error-proneness out of the drugework sourcecode.

      That brings up an interesting point - has anyone created something like C++::Template or Lang::C++::Template? Would there be a use for it? How would one go about specifying it?

      ------
      We are the carpenters and bricklayers of the Information Age.

      Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

        I simply used Text::Template to generate C code, it worked without any problems and too many escape codes.

        Just my two cents, -gjb-

Re: Be grateful for Perl OO
by pdcawley (Hermit) on Jan 28, 2003 at 19:13 UTC
    It's a bit hard to know what you're trying to do here, but I really don't like that hash of methods. Consider making the return value of GetID into an object and have $some_param dispatch to it. Then your switch becomes
    for my $i (0 .. $SomeValue - 1) { $SomeArray[$i]{value} = $some_param->dispatched_method() } sub SomeParamClass::dispatched_method { my $self = shift; $self->getID->dispatch_method_to($self); }
    You can move any other 'switched' behaviour onto your ID classes and get rid of multiple CaseToMethod hashes in the process. If you think that making the ID into an object is overkill then you can at least move the CaseToMethod hashes into your parameter class's defintion. Or, if the method called only varies with the class of the parameter just create a dispatched_method methods on each parameter class.
Re: Be grateful for Perl OO
by John M. Dlugosz (Monsignor) on Jan 28, 2003 at 17:26 UTC
    Class_A and Class_B share an interface, but not a base class

    If you can't factor out the interface as a base class because you're not allowed to touch the code, just use a template.

    Since all the functions have the same signature (that is, no parameters) you can use pointers to member functions in C++.

      Yes, functors created using templates would help a lot. See Modern C++ Design for some amazing things you can do with C++ templates.

      Update: The basic idea is using a templated function like this:

      template <class Type> double doCall(Type *instance, double (Type::*pmf)()) { return (instance->*pmf)(); }
      Update 1/19/03: Transposed the return type to the proper place in the declaration. Thanks to John M. Dlugosz.
Re: Be grateful for Perl OO
by Steve_p (Priest) on Jan 30, 2003 at 15:38 UTC

    The problem does not seem to be that Perl is better, it just seems like your system is in a serious need for refactoring. Here we go!

    Step 1 - get a parent class for A and B. If they have the same interface in the code, inforce it and make your life easier. Based one the code, then, SomeFlag and SomeValue become irrelevant, so....

    for (int i=0; i < SomeValue; i++) { Class_P *p; //parent class int SomeID; p = (Class_p)* some_param; SomeID = p->getID;

    Step 2 - Eliminating the switch...case. Many times, a case statement indicates some problems with the abstraction. So, lets create a new class called SystemCase for our purposes. It will be abstract with a class method called getInstance. Each individual case will be its own object. Since I can't beleive that these cases are only relevant in this chunk of code, this should help elsewhere. Since all of this is decided by the getID() method of p, there are some additional refactorings gained later...

    class SystemCase{ public: SystemCase getInstance(Class_P *p){ switch(p->getID()){ case FIRST_CASE: return FirstCase(p); ... } virtual SomeArrayType runMethod(); }; class FirstCase{ public: SomeArrayType runMethod(){ ... } };

    So, that takes the whole chunk of code to...

    for (int i=0; i < SomeValue; i++) { Class_P *p; //parent class int SomeID; p = (Class_p)* some_param; //SomeID = p->getID() isn't needed since we look at this in System +Case SystemCase sc = SystemCase::getInstance(p); SomeArray[i].value = sc.runMethod(); }

    Eleven lines here, and the code is much more clear than before. You did a great job refactoring the code in Perl, but the same thing can be just as easily done in C++. As I mentioned above, if getID() is determining values to run elsewhere, the above refactoring should help out throughout your system.

      Wonderful reply, and exactly what I would do if I had the time, resources, and was able to convince my business owners that refactoring was in their best interests. I didn't even mention the worst part - this code is replicated between an C++ class and a C++ stand-alone. But, they're in the same application and there's no time budgeted to combine them. (The testing time alone to re-factor would be large.)

      In addition, even if I could re-factor, I probably wouldn't create a common base class between ClassA and ClassB. From an IS-A perspective, they don't share a common ancestor. They just share a common interface. I'm not sure I'd want to create a fictional ancestor just to describe a common interface. (Though, that could just be the Perl in me talking. *grins*)

      ------
      We are the carpenters and bricklayers of the Information Age.

      Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

        The testing time alone to re-factor would be large
        Ahh...a lesson for next time. If you have good unit tests from the start then you can refactor all you like; your unit tests will tell you if you've broken something. This is one of the tenets of Extreme Programming and for me was an "aha!" moment when I fully grok'd why it was so important.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://230610]
Approved by broquaint
Front-paged by broquaint
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others taking refuge in the Monastery: (3)
As of 2024-04-24 21:05 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found