Simulating aspects of static-typing in a duck-typed language
Asked Answered
S

3

10

In my current job I'm building a suite of Perl scripts that depend heavily on objects. (using Perl's bless() on a Hash to get as close to OO as possible)

Now, for lack of a better way of putting this, most programmers at my company aren't very smart. Worse, they don't like reading documentation and seem to have a problem understanding other people's code. Cowboy coding is the game here. Whenever they encounter a problem and try to fix it, they come up with a horrendous solution that actually solves nothing and usually makes it worse.

This results in me, frankly, not trusting them with code written in duck typed language. As an example, I see too many problems with them not getting an explicit error for misusing objects. For instance, if type A has member foo, and they do something like, instance->goo, they aren't going to see the problem immediately. It will return a null/undefined value, and they will probably waste an hour finding the cause. Then end up changing something else because they didn't properly identify the original problem.

So I'm brainstorming for a way to keep my scripting language (its rapid development is an advantage) but give an explicit error message when an object isn't used properly. I realize that since there isn't a compile stage or static typing, the error will have to be at run time. I'm fine with this, so long as the user gets a very explicit notice saying "this object doesn't have X"

As part of my solution, I don't want it to be required that they check if a method/variable exists before trying to use it.

Even though my work is in Perl, I think this can be language agnostic.

Solvable answered 29/5, 2010 at 1:50 Comment(5)
Given that you're in this environment, writing in Perl while admitting that your coworkers don't understand it... It sounds like you're part of the problem!Crosier
i'm not using Perl by choice. it was either this or sh according to management.Solvable
i'm in a position of some leadership, but the bureaucrats make technical decisions. i'm working to change this, but i still don't have as much influence as i need.Solvable
Sounds like you have a social problem you're trying to solve with technology. The technology will help in the short term, but unless you educate your coworkers, rather than working with you they're likely to resent the restrictions, seeing them as obstacles, and work around them.Exercitation
Do you mean you want to make $instance->{member} throw an error so programmers don't grab at the guts of objects and inside use accessor methods?Exercitation
L
17

If you have any shot of adding modules to use, try Moose. It provides pretty much all the features you'd want in a modern programming environment, and more. It does type checking, excellent inheritance, has introspection capabilities, and with MooseX::Declare, one of the nicest interfaces for Perl classes out there. Take a look:

use MooseX::Declare;

class BankAccount {
    has 'balance' => ( isa => 'Num', is => 'rw', default => 0 );

    method deposit (Num $amount) {
        $self->balance( $self->balance + $amount );
    }

    method withdraw (Num $amount) {
        my $current_balance = $self->balance();
        ( $current_balance >= $amount )
            || confess "Account overdrawn";
        $self->balance( $current_balance - $amount );
    }
}

class CheckingAccount extends BankAccount {
    has 'overdraft_account' => ( isa => 'BankAccount', is => 'rw' );

    before withdraw (Num $amount) {
        my $overdraft_amount = $amount - $self->balance();
        if ( $self->overdraft_account && $overdraft_amount > 0 ) {
            $self->overdraft_account->withdraw($overdraft_amount);
            $self->deposit($overdraft_amount);
        }
    }
}

I think it's pretty cool, myself. :) It's a layer over Perl's object system, so it works with stuff you already have (basically.)

With Moose, you can create subtypes really easily, so you can make sure your input is valid. Lazy programmers agree: with so little that has to be done to make subtypes work in Moose, it's easier to do them than not! (from Cookbook 4)

subtype 'USState'
    => as Str
    => where {
           (    exists $STATES->{code2state}{ uc($_) }
             || exists $STATES->{state2code}{ uc($_) } );
       };

And Tada, the USState is now a type you can use! No fuss, no muss, and just a small amount of code. It'll throw an error if it's not right, and all the consumers of your class have to do is pass a scalar with that string in it. If it's fine (which it should be...right? :) ) They use it like normal, and your class is protected from garbage. How nice is that!

Moose has tons of awesome stuff like this.

Trust me. Check it out. :)

Lennon answered 29/5, 2010 at 3:9 Comment(2)
Does Moose have a way of turning $object->{member} into an error? I think that's what the OP wants.Exercitation
Look up MooseX::InsideOut. It should make $object->{member} invalid, while still being able to use all the antlered goodness described above.Remontant
N
5

In Perl,

  • make it required that use strict and use warnings are on in 100% of the code

  • You can try to make an almost private member variables by creating closures. A very good example is "Private Member Variables, Sort of " section in http://www.usenix.org/publications/login/1998-10/perl.html . They are not 100% private but fairly un-obvious how to access unless you really know what you're doing (and require them to read your code and do research to find out how).

  • If you don't want to use closures, the following approach works somewhat well:

    Make all of your object member variables (aka object hash keys in Perl) wrapped in accessors. There are ways to do this efficiently from coding standards POV. One of the least safe is Class::Accessor::Fast. I'm sure Moose has better ways but I'm not that familiar with Moose.

    Make sure to "hide" actual member variables in private-convention names, e.g. $object->{'__private__var1'} would be the member variable, and $object->var1() would be a getter/setter accessor.

    NOTE: For the last, Class::Accessor::Fast is bad since its member variables share names with accessors. But you can have very easy builders that work just like Class::Accessor::Fast and create key values such as $obj->{'__private__foo'} for "foo".

    This won't prevent them shooting themselves in the foot, but WILL make it a lot harder to do so.

    In your case, if they use $obj->goo or $obj->goo(), they WOULD get a runtime error, at least in Perl.

    They could of course go out of their way to do $obj->{'__private__goo'}, but if they do the gonzo cowboy crap due to sheer laziness, the latter is a lot more work than doing the correct $obj->foo().

    You can also have a scan of code-base which detects $object->{"_ type strings, though from your description that might not work as a deterrent that much.

Necrophilism answered 29/5, 2010 at 1:58 Comment(1)
@jrockway - fair enough - bad wording on my part. I'll remove the exaggregatrion. But as I said in the "private by name" option, the name of the game is "Make doing the Bad Thing harder than Good Thing".Necrophilism
E
4

You can use Class::InsideOut or Object::InsideOut which give you true data privacy. Rather than storing data in a blessed hash reference, a blessed scalar reference is used as a key to lexical data hashes. Long story short, if your co-workers try $obj->{member} they'll get a run time error. There's nothing in $obj for them to grab at and no easy way to get at the data except through accessors.

Here is a discussion of the inside-out technique and various implementations.

Exercitation answered 29/5, 2010 at 21:57 Comment(2)
I remember there being a lot of talk about inside-out objects some years ago and they seemed to be the new hotness, but they've pretty much dropped off the radar these days. Any idea why they fell out of favor? Are there actual issues with them or were they just eclipsed by Moose?Counteroffensive
AFAIK most of the issues that made them problematic have been solved, it was an important feature of 5.10 (which has been backported to 5.8) to add field hashes (see Hash::Util::FieldHash) to properly support them. Maybe now that the implementation issues have been worked out its no longer worth talking about?Exercitation

© 2022 - 2024 — McMap. All rights reserved.