Implementing Interfaces and Type Safety (OOP Newbie in Perl)

Discussion in 'Perl Misc' started by Veli-Pekka Tätilä, Aug 17, 2005.

  1. Hi,
    I'm new to OOP in Perl having only used classes by others, read some book's
    take on the subject and implemented closures for quick-n-dirty object-like
    thingies. One thing I didn't see mentioned in Beginning PErl is Perl's take
    on what Java calls interfaces and C++ programmers pure virtual abstract base
    classes. That is in stead of a real class with state or useful methods, it
    is something non-instanciable - a contract that concrete sub-classes will
    have a certain set of methods in them. Is there a good way to implement an
    interface in Perl or some perlish idiom for achieving the same thing?

    I've noticed that most Perl classes don't rely too much on abstraction where
    as in Java I'm almost always talking through some sort of interface. Don't
    get me wrong, though, usually I like Perl's simple and practical ways of
    achieving what you want, <grin>.

    Also, if there's such a thing as an interface in Perl, how is it used and
    what about type safety? In Java one would get a reference to the interface
    and can assign to it any class implementing the interface. Java will then
    automagically see that the right method gets called inside the class and we
    have polymorphism. Further more, type checking is done at compile time for
    the most part.

    From what I've understood objects are actually blessed references in Perl
    and when assigning to a reference, the type of the referent may vary freely.
    Thus the best you can do is to keep assigning the right sub-classes derived
    from a dummy interface class and hope that you don't make any mistakes in
    the process, right? The only trouble with that is accidentally assigning
    something that does not conform to the interface . Perl will only throw an
    error at compile time should the missing method or methods get called. I've
    noticed that even in trivial cases, typoed method names are not catched when
    checking the syntax. PHP 4 does come to mind, argh, but fortunately Perl's
    OOP stuff is not at all as toy-like as it used to be in PHP.

    In contrast, should one try using operators, functions or references with
    the wrong primitive type e.g. hash functions for arrays, you'll usually get
    at least a compile-time error before the app is run. is there any way of
    enforcing similar type safety for user-created objects?

    I suppose you could use reflection to some effect with the methods in the
    universal base class. Checking whether the referent is derived from the
    interface by calling isa or asking if it does support a particular method
    with the appropriately named method can.

    Is there a better way than reflection i.e. relying on prototypes or being
    able to strongly type references?

    Lastly, speaking of Perl's OOP stuff, I remember a funny quote about modules
    from the Camel book:

    <cite>
    Perl does not patrol private/public borders within its modules - unlike
    languages such as C++, Ada, and Modula-17, Perl isn't infatuated with
    enforced privacy. As we mentioned at the beginning of the chapter, a Perl
    module would prefer that you stayed out of its living room because you
    weren't invited, not because it has a shotgun.
    </cite>
     
    Veli-Pekka Tätilä, Aug 17, 2005
    #1
    1. Advertisements

  2. Also sprach Veli-Pekka Tätilä:
    Yes, it can be simulated in a fairly simple manner. The idea is to write
    a package where each method dies on invocation. However, what you cannot
    easily have is compile-time checks that ensure that a class implementing
    that interface does in fact implement (=override in this case) all
    methods.

    Here is a simple interface:

    package Interface;

    use Carp;

    sub new {
    my $class = shift;
    croak "'$class' does not implement 'new'";
    }

    sub method1 {
    my $self = shift;
    my $class = ref $self;
    croak "'$class' does not implement 'method1'";
    }

    # etc.

    If you want to be smart and avoid typing repetitive code, have perl
    generate the methods for you based on a list of methods that must be
    implemented by subclasses:

    package Interface;

    use Carp;

    my @MUST_IMPLEMENT = qw/new method1 method2/;

    for (@MUST_IMPLEMENT) {
    no strict 'refs'; # when strictures are enabled
    *$_ = sub {
    my $invoc = shift;
    my $class = ref($invoc) || $invoc;
    croak "'$class' does not implement '$_'";
    };
    }
    1;
    __END__

    A class implementing the above interface would look like this:

    package Implementation;

    use base qw/Interface/; # subclass 'Interface'

    sub new {
    ...
    }
    sub method1 {
    ...
    }
    # etc.

    1;
    __END__

    When it comes to object-orientedness, everything happens at runtime as
    far as Perl is concerned. What you say about assigning an object to a
    variable of an interface type in Java (and the appropriate type checks
    at compile time) does not exist for Perl and I would assume there is no
    way to do it at compile-time. There are ways, however, to ensure no such
    assignments happens at run-time, but these need to be implemented
    manually, via a tied interface. But then this is quite heavy stuff.
    That is true. Mistyped method invocations are caught at runtime. They
    have to be due to Perl's super-polymorphic nature. Consider that a
    particular method might not exist at compile-time. It might be added
    later at run-time...alas, a whole class might suddenly spring into
    existance during run-time. Lack of compile-time checks is the price you
    have to pay when using a dynamic language such as Perl.
    No. The reason why it works with functions is that functions don't have
    inheritance involved. No dynamic dispatch then means that many more
    things can be checked at compile-time. Note however that a mistyped
    function name is still a run-time error. The errors at compile-time that
    you mention are those concerning functions with a prototype:

    sub func (\@) {
    "I must receive an array";
    my $ary_ref = shift; # it's passed as reference
    }

    my @ary = (1 .. 10);
    func @ary; # ok

    func 1 .. 10; # compile-time error
    func $scalar; # as is this
    func %hash; # or this

    Perl-builtins have prototypes attached to them so there perl can make
    some sanity checks on the validity of the arguments at compile-time.
    Yes, this is what I referred to earlier with "but these need to be
    implemented manually". Run-time checks such as isa() or ref() are the
    ones most commonly found to catch type violations.
    Prototypes don't exist for methods. Strongly typing references is
    possible to a certain limited extent. See 'perldoc fields'.
    That's precisely what Perl is about. It's a matter of getting used to
    it. This very liberal approach may be frowned upon by the Ada language
    designers, however each has its own virtues and shortcomings. The
    non-enforced privacy in Perl doesn't exist because the designers of Perl
    were too dumb to implement it. It was a deliberate decision.

    Tassilo
     
    Tassilo v. Parseval, Aug 17, 2005
    #2
    1. Advertisements

  3. Tassilo v. Parseval wrote:

    defencive programming:
    Yup, at least V4 is <smile>. But as to defencive programming in Perl, I've
    found these two lines invaluable:

    use strict;
    use warnings FATAL => qw|all|;

    Writing code with speech it is far too easy to accidentally mistype an
    identifier and try introducing a new variable in the middle of a
    print-statement or something. And as to making warnings die, it prevents you
    from fixing things lazily and catches subtle initialization bugs, very nice.
    Another benefit is knowing as soon as something goes wrong. As the screen
    reader's focus can be in one GUI widget at a time and the console app I'm
    doing runs in the background, dying quickly, forces me to actually notice
    the warnings. It would be even better if I could be warned audibly by
    emiting the bell char \a to the console when-ever this does happen. However,
    I reckon that's a no-can-do in Win32 as signals aren't implemented and END
    blocks not run when you die. Perhaps using eval, then.

    benefits of dynamic typing:
    I see, your auto load example is pretty impressive. Still, as they say
    there's more than one way to do it, wouldn't it be easier foor the caller if
    there was one createTag method whose first argument was the name of the tag.
    No auto-load or pollution of the callers namespace needed. But then again
    the other benefits you mentioned will be lost and you don't necessarily have
    to export the methods.

    ah, I think I now know another example of auto loading. The Win32::OLE
    module that I'm using a lot enables you to load in an OLE type library and
    after that the constructed object will magically have method and property
    names corresponding to those of the type library. And calling non-existant
    methods dies cleanly, too. I was really awed when I saw this. I cannot think
    of a way to achieve the same thing in Java, for instance, even if you could
    use reflection and polymorphism. Sure you might be able to have some
    dispatcher method whose first argument is the property or method being
    operated on, as in the previous example, but that doesn't look as smooth as
    in Perl. Finally, I'm greatful that Perl programmers generally don't do as
    much operator over-loading as C++-programmers, because it can be very
    confusing if overused.

    Prototypes:
    I just tried your example and noticed the same thing on my own yesterday.
    Argh as in C again, I like the Java method where the compiler doesn't force
    you to type in the same thing twice for no obvious reasons: I mean obvious
    from the programmer convenience point of view.

    Anyway, what's the canonical way of including these protos? If I stick all
    of them at the beginning of the file, it is confusing for people reading it
    the first time. But then again I haven't seen people using C-like include
    files for definitions, either, and am unsure as to what extension such files
    should have. ph comes to mind standing for perl header.

    If I want to be truely lazy, is there a way of automatically copying the
    prototype from the implementation and placing it before the function is
    being used? I hope someone else has already done something like this and
    will search using PPM. Source filtering comes to mind as the first choice.
     
    Veli-Pekka Tätilä, Aug 19, 2005
    #3
  4. Also sprach Veli-Pekka Tätilä:
    Don't the pseudo-signals __WARN__ and __DIE__ work on windows, too?
    With them:

    use warningsd FATAL => 'all';
    $SIG{ __DIE__ } = sub {
    for (1 .. 10) {
    print STDERR "\a"; # need auto-flushed handle here
    select undef, undef, undef, 0.2;
    }
    }

    Or use one of the Win32 modules to emmit this annoying "oh-oh" Windows
    system sound. :)
    Yes, having a createTag() method is how less dynamic languages would do
    it. With the expected consequences for the API.
    Note that tied hashes or arrays are conceptually overloading the
    subscript operator. The C++ equivalent would be overloading the '[]'
    operator. So if you consider tying as overloading, then it is in fact
    common in Perl.

    However, Perl programmers seem to be less hooked on overloading
    operators the classical way (by using the 'overload' pragma).
    The need for pre-declarations in certain languages has to do with the
    way the compilers are implemented. You need at least a two-phase
    compiler (that is, a compiler that makes two sweeps over the program or
    the intermediate internal representation of the program) to eliminate
    the need for forward declarations. However, there are also things like
    incremental compilation where it is crucial for the compiler to know the
    calling conventions for certain functions that are used in the code to
    be compiled but that are not yet defined.

    I reckon perl tries to reduce the number of visits of each syntax tree
    node as it increases compile-time. Forward-declarations are one way to
    ensure that prototypes can be checked at compile-time without the need
    of running through the abstract syntax tree too often.
    Perl headers are already used for something else and they have .ph as
    extension. See 'perldoc h2ph'. It's fairly obscure.

    Just use .pm as extension which is also the only way that ensures you
    can include it with 'use'. If you want to indicate that a module only
    consists forward declaration use an appropriate naming scheme:

    Module_inc.pm # or Module_fwd.pm or so
    Module.pm

    Note that the file containing the forward declarations needs to be
    included at compile-time in order to have any effect. When you include
    modules via 'use', this happens anyway.
    Source filtering is probably the only way as it happens early enough,
    namely at scan-time. But it's a heavy and in parts error-prone weapon.
    But then I don't entirely understand your issue. If you have functions
    in a module then you need to include this module in your programs
    anyway. Do it with 'use' and no issues will arise.

    Tassilo
     
    Tassilo v. Parseval, Aug 19, 2005
    #4
  5. I'm not actually sure having never delt with signals before. It would seem
    your example compiles but as soon as I try doing something after this sig
    handler definition, I get a syntax error. Most likely my bad, though.
    I've turned all system sounds off explicitely so I'm not sure what It'll
    play if anything. Maybe the good old standard beep from the PC Speaker.

    Overloading:
    Yes, it seems so. Still I don't mind, that's awfully convenient and the
    semantics are clear, too, especially the database examples I've seen. As to
    bad operator overloading I've never liked the bitshits doing printing and
    input in C++ as there's already a clearly defined meaning for those ops.
    Still you have to choose some existing operator to overload if any,

    Prototypes:
    I guessed that, though I must confess I know next to nothing about compiler
    design. Another logical way to look at it is that it's unlikely that Perl
    would have intentional, syntactic sault for dealing with prototypes. It is
    easy to typo either of the two protos, the forward declaration or the actual
    proto before the function body, which results in a prototype mismatch. C's
    include files, forward declarations and a zillion other things have caused
    me so much grief and frustration that I'm glad higher-level languages like
    Perl or Java get well around these issues.
    Yes, I've noticed. Source filtering was suggested to me in an earlier post
    about making Perl operators English like or such that they read out well in
    speech. I discovered after a while that it is a mixed blessing. As soon as
    you have to work on a system that's just standard, it does help if you
    haven't tweaked things an awful lot at home.
     
    Veli-Pekka Tätilä, Aug 19, 2005
    #5
  6. Also sprach Veli-Pekka Tätilä:
    Ah, sorry, as always I've forgotten the trailing semicolon behind the
    assignment of the closure. Afterall, it's an ordinary statement and no
    named function definition:

    $SIG{ __DIE__ } = sub {
    ...
    };

    Now it'll work.

    Tassilo
     
    Tassilo v. Parseval, Aug 20, 2005
    #6
  7. PerlUnit

    http://perlunit.sourceforge.net/


    Mark
     
    Mark Clements, Aug 20, 2005
    #7
  8. Ah, has happened to me loads. The same thing with Java's anonymous inner
    classes.
    Hey thanks a bunch, works very well. In addition to running in a very strict
    mode I managed to setup myself a nice die handler. It emits the warning beep
    and prints out the error on screen. This is standard stuf so far, though.
    Additionally it uses the system's default SAPI voice, if available, to read
    out loud the "dier message". Highly cool and saves some keyboard commands as
    a screen reader user.
     
    Veli-Pekka Tätilä, Aug 20, 2005
    #8
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.