dabreegster has asked for the wisdom of the Perl Monks concerning the following question:

Hey. I've been experimenting with many new methods of laziness for my roguelike lately, and one of them dealt with the customizability of objects. I've started to incorporate a concept of dynamic attributes, where any attribute of an object can be determined on-the-fly with anonymous subroutines. In other words, $player->{strength} could be a simple value of 14 or a sub returning the current value of $player->{rawstrength} + $weapon->{str_mod} or whatever. Since I use attributes many times in code, I don't want to have to constantly check to see if an attribute is a simple value or an anonymous subroutine. So I wrote a simple interface with tie to make accessing attributes evaluate the anonymous sub automatically and return the value. It works great, but now there's a problem. If I have some other subroutine that needs to take in arguments, I can't use it. $foo->{bar}->($baz) results in Perl trying to dereference whatever $foo->{bar}->() spits out.

To my knowledge, there's no way around this. I don't think there's a way to see how the actual anonymous subroutine is being used. Now that I think about it, situations where dynamic attributes as anonymous subs that take arguments won't really crop up too often. I'm still handling design tentatively. But if they do, is there a solution? Advice is appreciated.

If code is needed, I'd be glad to paste what I have. But all I've got is a tie interface that auto-evaluates anonymous subroutines.

I guess the real solution is to obey proper OOP rules and use real methods. It's just that the syntax is awkward. I'm trying to use the tie interface to create encapsulation that I need, but I'm running into some limitations. I sure hope Perl 6 will remedy these well.

  • Comment on Using tie to return value of anonymous sub

Replies are listed 'Best First'.
Re: Using tie to return value of anonymous sub
by DrWhy (Chaplain) on Feb 17, 2006 at 03:24 UTC
    How is proper OOP Syntax awkward? Are you talking about awkwardness of defining the class or awkwardness of using the class? I would think that the syntax of using classes is simpler than what you have. E.g.
    Your way: $player->{strength} OOP way: $player->strength Your way: $foo->{bar}->($baz) OOP way: $foo->bar($baz)
    Defining simple accessor/mutator methods is very straightforward, and there are many modules in CPAN that make it even easier. Spiffy is one of the most popular these days, but there are many others.

    --DrWhy

    "If God had meant for us to think for ourselves he would have given us brains. Oh, wait..."

      Spiffy is one of the most popular these days, but there are many others.
      Not exactly. INGY wrote it, and he creates many many modules, so puts it in many many modules.

      Well, both. I guess it's just a personal thing with OOP syntax. I'm in favor of using hashes both as an implementation and an API. But it's tough to do the latter in some cases.

      Mainly, the place where the OOP method really breaks down is multidimensional data structures. How is something like $player->{react_before}{poisoned} to be used? $player->react_before->{poisoned}? Something even more awkward?

      Either way, I guess the real problem is syntax and I guess the real solution is to find a scheme for real methods that I like. Until proper APIs can be created with tie and hashes, I guess I haven't a choice. Thank you.

        Assuming player.react_before.poisoned (to use a neutral syntax since syntax is the topic) sounds like a way of setting (and getting?) an event handler, how about something like the following:

        sub name { my ($self, $name) = @_; $self->{name} = $name if @_ >= 2; return $self->{name}; } sub react_before { my ($self, $event, $handler) = @_; if (@_ >= 2) { $self->{event_handlers}{$event}{before} = $handler; } return $self->{event_handlers}{$event}{before}; } sub poisoned { my ($self, $poisoned) = @_; if (@_ >= 2) { if ($poisoned != $self->{poisoned}) { my $handler= $self->{event_handlers}{poisoned}{before}; $poisoned = $handler->($self, 'poisoned', $val) if $handler; } if ($val != $self->{poisoned}) { $self->{poisoned} = $poisoned; my $handler = $self->{event_handlers}{poisoned}{after}; $handler->($self, 'poisoned', $poisoned) if $handler; } } return $self->{poisoned}; } $char->react_before(poisoned => \&handler); $char->poisoned(1); print( $char->name, ' is ', $char->poisoned ? 'posioned' : 'not poisoned', ".\n" );
        One of the things I really like about Perl is that it leaves room for personal preference in terms of how to implement things. You can mix and match different programming paradigms as you see fit. You can go functional in domains where that makes most sense, OO where that makes sense, and so on.

        That said there are times where 'personal preference' gets in the way of writing the best code. If you (generic) are opposed to OO in all circumstances due to personal preferences you are likely to write code in a way that is one or more of more complex, more buggy, and more difficult to extend and maintain than it would be otherwise. I have a sense here that you may be going in that direction here. Seems like to me that you are going through some awful complex gyrations to avoid OO syntax, but of course you are really using OO since tie just let's you do OO while making it look like you are doing some other kind of programming. This will give you a performance hit (though I'm not sure how much the tie-ing overhead really hits performance) which can be an issue in some types of computer games (I don't know about your particular case though).

        --DrWhy

        "If God had meant for us to think for ourselves he would have given us brains. Oh, wait..."

Re: Using tie to return value of anonymous sub
by dragonchild (Archbishop) on Feb 17, 2006 at 15:20 UTC
    Note: I typed this code straight into the textbox. I have no clue if it will even compile, let alone work as intended.
    package Dabreegster::Attribute::Maker; use strict; use warnings; # if $] >= 5.6.0; sub import { shift; my $pkg = caller; foreach my $attr ( @_ ) { # We're doing symbol-table manipulation here, so # the prohibition against soft references needs to # be relaxed, but only within this context. no strict 'refs'; my $getter = "${pkg}::${attr}"; my $setter = "${pkg}::set_${attr}"; # We will only define the function if the function # hasn't already been defined. *{ $getter } = sub { my $self = shift; if ( (Scalar::Util::reftype( $self->{$attr} ) || '') eq 'C +ODE' ) { return $self->{$attr}->( @_ ); } else { return $self->{$attr}; } } unless defined \&{ $getter }; *{ $setter } = sub { my $self = shift; if ( @_ ) { $self->{$attr} = shift; } return $self; } unless defined \&{ $setter }; } return; } 1; __END__
    Then, you would use it as so:
    package Character::Player; use Dabreegster::Attribute::Maker qw( str dex con int wis cha ); # Now, you have access to the following functions: # str(), set_str(), dex(), set_dex(), etc.
    This code plays nicely with inheritance so long as all your object representations are hashrefs. But, given your original design, I don't think that's going to be a problem.

    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?