Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Re: Runtime introspection: What good is it?

by moritz (Cardinal)
on Jul 06, 2008 at 17:36 UTC ( [id://695839]=note: print w/replies, xml ) Need Help??


in reply to Runtime introspection: What good is it?

Premise: There's nothing that can be done with run-time introspection that cannot be done (better) by compile-time decision taking.
sub what_does_it_return { return rand(1) > 0.5 ? [] : {}; } # bad example, but illustrates the essence

Sometimes your data comes from the outside, (for example through a serialization), and if you don't know its structure in advance, you have three choices:

  1. Press everything into the same structue (for example through normalization, and then store everything into SQL-like tables)
  2. Create wrapper classes that let you use a fixed static type
  3. Create dynamic classes at run time, and work with reflection / run-time inspection

Number one is unhandy and ugly, and number two basically means building a (primitive) meta-object protocol that allows run time inspection on top of the existing object model.

If the object model of your programming language is powerful enough, there's no need for that. You can just use native objects for data with a format that's not known at compile time. Which implies less code to write, specifically one abstraction layer that you don't need anymore.

Replies are listed 'Best First'.
Re^2: Runtime introspection: What good is it?
by BrowserUk (Patriarch) on Jul 06, 2008 at 18:14 UTC
    1. Use compile-time overloading.

      Eg.

      use classA; use classH; sub what_does_it_return { return rand(1) > 0.5 ? bless( [], 'classA' ) : bless( {}, 'classH' ); } my $o = what_does_it_return( ... ); $o->method( ... );

    Viola. No introspection needed.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.

      Sigh. Okay, lets try and type your code, cause if we don't check your type usage at compile time then it could just randomly blow up at runtime, which is not acceptable in most cases (and in worst case could open your code to exploits).

      sub what_does_it_return () return classA | classH { return rand(1) > 0.5 ? bless( [], 'classA' ) : bless( {}, 'classH' ); }
      So, assuming our language allows it, we can say that your function will return a type "union", meaning a value of either a classA or a classH type. So, now, lets try and use the variable that is returned.
      my classA | classH $x = what_does_it_return();
      So, the $x variable must be typed the same as the return value of the function, so it is again a type union. So now lets use $x.
      $x->something()
      Okay, so thats fine assuming that classA and classH all respond to the same methods right? Nope, it is not. Lets look at the definition for classA::method.
      package classA; sub something (classA $self) { ... }
      Our $x is a type classA | classH not classA, so it does not pass the type constraint for it's own method. This can't be good.

      But wait, maybe you made classA and classH both derived from the same superclass, lets call it classX. Lets re-type your function now:

      sub what_does_it_return () return classX { return rand(1) > 0.5 ? bless( [], 'classA' ) : bless( {}, 'classH' ); }
      Does this buy us anything? Nope, failed again, because there is no "something" method in classX, so the compiler generates an error when you try and do this:
      my classX $x = what_does_it_return(); $x->something() # compile-time blowup, classX doesn't have the "someth +ing" method!

      Okay, so maybe you don't care about types, does this invalidate my points? Nope, because your code still makes the assumption that classA and classH are 100% interchangeable in all cases, they they can be easily substituted for one another anywhere in your code and Just Work. This is an ideal case, and one that only works in very restricted cases and pretty much in non-trivial programs only. The moment your bring in outside code that knows nothing of the interchangeability of classA and classH you open yourself up for a lot of errors, errors that will almost certainly happen at runtime (remember you gave up compile-time type checking already).

      -stvn
      A reply falls below the community's threshold of quality. You may see it by logging in.

      It should be noted that this code:

      $o->method( ... );
      requires runtime introspection to work.

      Perl does not determine the method being called at compile time, instead it will lookup in the @ISA in a depth-first search to find the package which has a method of the name "method". The -> is an operator, that operator is a function just like any other function either built-in or defined by you. By calling the code of that operator you are explicitly telling Perl to do some runtime introspection to find and execute a method for you. So, hmm, I think maybe that saying:

      Viola. No introspection needed.
      is not quite correct.

      -stvn

        Perl is interpreted. If you choose not to see the big distinction between those operations that are required for the operation of the language, and those (like UNIVERSAL::ISA and UNIVERSAL::can), that are not, then you're conducting an entirely different discussion to the one I set out to have.

        If you blessed (or tied) all your hashes, arrays and scalars, and provided clone() methods in each of those packages, then no introspection (in the sense of application programmer explicitly coded type-based dispatching), is required.

        Before you counter argue further, please think about why I might have asked the question I asked. Think about which side of this argument I might normally hang my hat. And think about why I might have phrased the premise in the way I did.

        It's unfortunate that you taken the stance you have because it means that I will now not get the responses I had hoped for. Ie. Real examples of how people are using explicit, programmmer driven, class-based introspection, in Perl. Ie. The very sort of stuff that Moose provides for. Ie. The very use cases that make the case for using Moose in the first place.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
      That way you add "alien" methods to your data structures, which are the wrappers I wrote about.

      The problem with wrappers is that they aren't very re-usable. In the example above you assumed that the sub what_does_it_return is in your own code base, or easily overridable. That's not necessarily the case. So you have to hope that whoever wrote the module you're using has built proper, reusable and maintainable wrappers.

      In contrast when you have introspection built into your language's OO model, you can just use that, and it will work fine.

      Another problem with overloading is that it doesn't scale very well. If you have a million data objects, why should you overload them all if you just want to call a method on one of them?

      Both of my points have in common that you need to plan in advance. You need to know or guess how the result objects of, for example, a de-serializer will be used. But you can't, because in the general case the one who uses your module is cleverer that you and, and more creative, and most of all he's crazier. He'll get some weird ideas of what to do, and there won't be wrappers in place, so he'll have to resort to something different.

      So runtime introspection is at once lazy and humble - lazy because you don't try to guess in advance all use cases, and humble because you don't think you can guess them all.

      That's the reason why Perl 6 has a pluggable object model that allows introspection - if something is missing or gone wrong, it's relatively easy to add, fix or replace afterwards.

        That's the reason why Perl 6 has a pluggable object model that allows introspection - if something is missing or gone wrong, it's relatively easy to add, fix or replace afterwards.

        s/Perl 6/Perl/

        Perl 5 may not be as sexy as Perl 6, but it has just as flexible an object model, otherwise I would have never been able to write Moose or prototype the p6 model in it, not to mention all the Class:: stuff on CPAN that others have written.

        -stvn
        which are the wrappers I wrote about.... So you have to hope that whoever wrote the module you're using has built proper, reusable and maintainable wrappers.

        Those "wrappers" are just bog-standard 'subclasses'. And you'll quickly discover whether they are written correctly the first time you try to use a missing method, it'll fail.

        In contrast when you have introspection built into your language's OO model, you can just use that, and it will work fine.

        *&^£"*$^*"^£*$^. Sorry, but that just isn't the case. If you need to call method $o->doSomeThing(), and the objects class doesn't support that method, UNIVERSAL::can() will tell you that you can't call it, but it won't give you an alternative so what you gonna do?

        You have three choices:

        1. die
        2. warn and return undef.
        3. Try $o->doSomethingElse()?

        And you can, more easily and efficiently, do any of those without introspection:

        ... eval { $o->doSomething() }; $@ and die ...; # or $@ and warn( ... ) and return; ## or $@ and eval{ $o->doSomethingElse() }; $@ and eval{ $o->doYetSomethingDifferent() }; $@ and eval{ $o->${ ( $o->giveMeAListOfWhatYouCanDo() )[ 0 ] }() }; $@ and die "Apparently $o doesn't know how to do any damn thing!";
        Another problem with overloading is that it doesn't scale very well. If you have a million data objects, why should you overload them all if you just want to call a method on one of them?

        Again, not so. Outside of prototype-based objects (which are possible in Perl as this hoary old piece of code of mine from a million years ago shows), the adding of a method to a class or subclass doesn't affect the scalability of the class instances.

        Slightly untrue for the example of adding a clone() methods to Perl's built-in types, but as I've mentioned elsewhere, they are something of a special case, in as much as they do not act as objects unless you bless them. This is a similar quandary to the Java string vs. String (and to some degree StringBuffer) issue. Native types trade flexibility for performance. Performance may not be on the top of everyones list of priorities--indeed its not mine--but when you need it, you damn well better have it. Especially in the absence of deep, compile-time optimisation. Ie. In dynamic languages.

        If you have a million data objects, why should you overload them all if you just want to call a method on one of them?

        I can't wrap my head around this. It seems like the classic justifiction (non-argument) to me.

        If you have a million instances of some class:

        • Why would want to only perform some operation on one of them?
        • How do you know (guess?) that you'll only need to do it to one instance when designing your class?
        • Which instance will it be?
        • And how will you recognise (guess?) that instance at runtime?
        • If this instance is different from all the other instances, why is it an instance of this class in the first place?

        I could go on (and on, and on) about the principle and tenants of OO ... but I'll stop here :)

        Both of my points have in common that you need to plan in advance. You need to know or guess how the result objects of, for example, a de-serializer will be used. But you can't, because in the general case the one who uses your module is cleverer that you and, and more creative, and most of all he's crazier. He'll get some weird ideas of what to do, and there won't be wrappers in place, so he'll have to resort to something different.

        Again, not so. When you write (design) your class, you incorporate just those methods required for your application of that class. If you user wants more or different, he subclasses your class. Or uses a mix-in. Or composes his own class that has-a instance of your class. Or overrides your methods with his own. Or...

        Prior to the current moves to add RTTI to C++, there was no possibility of runtime introspection, and yet no one had problems doing found it impossible to do any of these things.

        It may be that there are some use cases for which introspection in easier or more efficient to code and debug. Or easier to comprehend. And it is these cases that I'm interested in hearing about. But so far, I haven't.

        So runtime introspection is at once lazy and humble - lazy because you don't try to guess in advance all use cases, and humble because you don't think you can guess them all.

        As a strong advocate of YAGNI, I'm very much in tune with the ideal of not trying to guess the future, or what uses a user may find for your code. But you do not need introspection to pursue or achieve that ideal. Indeed, you do not need OO to pursue that ideal! In fact, I would say that in many cases, OO is used when it is unnecessary and tends to breed the whole 'future proofing' spoof. But that's another argument :)

        But if you are going to use OO, I strongly believe that compile-time inheritance and composition have all the bases covered--from an 'it's possible' perspective. The only questions in my mind are:

        • Can run-time introspection simplify or clarify some of those bases?
        • What are the knock on costs in terms of (for example) trying to debug a system that modifies classes--and worse, instances--at runtime?

        This rings huge alarm bells in my head. Taking me back to the bad ol', bad ol' days of COBOL and Fortran compilers that emitted self-modifying code. I only had to try and debug a piece of that code once. Never--I say *NEVER*--again. Pouring over crash dumps trying to first find, and then decompile the code and stack traces to work out what code had been generated at run-time, that crashed. And that's before you could even begin to think about the un-captured, data-driven code path that lead to it being generated, or why what was generated, crashed.

        Never again!.

        That's the reason why Perl 6 has a pluggable object model that allows introspection - if something is missing or gone wrong, it's relatively easy to add, fix or replace afterwards.

        Maybe, a year or two from now Perl 6 will go live. And maybe, a year or five after that, I'll be looking back at this thread thinking: what was all the fuss about.

        Maybe, I'll reach a similar conclusion to that I have about OO. Used properly, without the 'everything must be' dogma, and not overused, or used for the sake of it, runtime introspection will become just another tool in the toolbox.

        But for right now, I remain decidedly unconvinced. I can see uses. I can see (some) convenience. But I cannot see any 'killer application'. And I'm very skeptical about the debug-ability and maintainability.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://695839]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others having a coffee break in the Monastery: (4)
As of 2024-04-18 01:49 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found