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

I'm doing some work on Class::Trait to fix a bug in its implementation. (A trait is similar to Perl 6 roles. Think of it as a cross between a mixin and an interface -- sort of). A trait can provide methods to whatever class uses that trait. However, to determine which methods it provides, Class::Trait currently examines the symbol table via keys %{"${trait}::"}. That causes a problem if functions or constants are imported from other modules. Those function are considered trait methods and get exported into the class using the trait. This is bad.

My first thought was to simply require traits to have a @PROVIDES array that lists which traits it provides (and I can possibly provide some sugar using attributes). However, traits can be composed of other traits and I'd need to ensure that those traits have their functions reexported into the class using the composed traits. My first thought is to do something like this:

package Trait::Foo; use Class::Trait 'base'; # the following adds their methods to @Trait::Foo::INHERITED_PROVIDES use Class::Trait qw(Trait1 Trait2); our @PROVIDES = qw/method1 method2/; # and in the Class::Trait::Base class: sub methods { my $class = shift; no strict 'refs'; return @{"${class}::PROVIDES"}, @{"${class}::INHERITED_PROVIDES"}; }

That can work and now each trait merely needs to declare the methods in the @PROVIDES array, but I was wondering if there was a cleaner way of going about this. If I stumble across a function in a symbol table, I don't suppose there's any way to know where it came from, is there? (short of using PPI, for example)

I suppose I can grab those trait methods as soon as use Class::Trait 'base'; is called, but this does require that programmer remembers to declare this prior to using any other module which might export something. I hate adding arbitrary restrictions like that, but it seems the simplest way. (Er, no. Those methods won't be defined yet so I'd have to parse the code. Bad idea.)

Cheers,
Ovid

New address of my CGI Course.

Replies are listed 'Best First'.
Re: Detecting an imported function
by diotalevi (Canon) on Nov 17, 2005 at 02:44 UTC

    Try checking the sub's compilation package. I'm using eval to cover just in case the value that was given to sub_package() isn't a code reference or whatever.

    use B 'svref_2object'; sub sub_package { eval { svref_2object( shift )->STASH->NAME } }

      So far this is looking like a great solution. It's eliminated the need for me to worry about other solutions. I may do more work with this in the future, but for now this is wonderful. Thanks!

      Cheers,
      Ovid

      New address of my CGI Course.

        By placing this at the end of my file, it runs after all use() and ->import has occurred. Any imported methods will be visible now. Because my caller called me with a use() call, this code is run during the BEGIN-time of my caller. This is what I imagined tye was suggesting.

        package Example; use vars qw( @Methods ); use Anything::You::Like; ... no strict 'refs'; @Methods = grep *{__PACKAGE__ . "::$_"}{CODE}, $keys %{__PACKAGE__ . " +::"}; 1;
Re: Detecting an imported function
by dragonchild (Archbishop) on Nov 17, 2005 at 03:46 UTC
    Have you tried Perl6::Roles? I wrote it for Tim for the DBI2 rewrite and he hasn't complained yet ...

    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?

      No, I haven't. Class::Trait is appealing because I am intimately familiar with it and it's a very complete trait implementation including conflict resolution, proper trait composition, enforcement of requirements, method aliasing, reflection, etc. In short, it has everything we need.

      Just glancing at your code suggests you might have the same problem that Class::Trait has:

      push @methods, map { [ $r, $_ ] } grep { *{"${r}::${_}"}{CODE} } keys %{"${r}::"};

      Where do those methods come from? Is there any chance a role might import a function (even as a constant) and have it exported?

      Cheers,
      Ovid

      New address of my CGI Course.

        Where do those methods come from?

        From the role's package. The current specification, as I understand it, is that anything a role can do, the class doing the role can do.

        Is there any chance a role might import a function (even as a constant) and have it exported?

        If Role->can('foo'), then Class->does('Role') implies Class->can('foo') for all foo in Role. If this isn't the spec, then I can provide a mechanism in Perl6::Roles that will account for that. Let say you define a "@ROLE_METH" or somesuch. But, the current P6 spec, as I understand it, says that the class will get everything.

        There is some discussion on p6l about whether or not the role will be able to reserve methods that are labelled private/protected/other, but, AFAIK, @Larry hasn't made a decision one way or the other.


        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?
Re: Detecting an imported function (exclude time)
by tye (Sage) on Nov 17, 2005 at 06:19 UTC

    For the heck of it, I'll suggest the low-tech approach of building a list of routines at BEGIN time and excluding these from the list of traits built at run time.

    It isn't obvious from a quick glance when / how the post-compilation moving of all subroutines happens (I guess when the trait-implementing package gets imported), but that must already be happening. So all you need to add is code to cache the list of to-exclude subroutines when Class::Trait::import is called (and then exclude them, of course).

    Of course, this means that the user needs to 'use' modules that import non-traits before 'use'ing Class::Trait. But it also means that the user can do creative things that 'import' subroutines that are meant to be traits... Which I find rather appealing.

    - tye        

      For what its worth, I'd prefer the flexibility tye's proposing than something as inlexible as I originally brought up (though it does answer the question very succinctly).

      How do I do I build a list of subs at BEGIN time without trying to parse Perl?

      package Foo; BEGIN { # &routine is not yet defined and therefore # can't be added to any list } sub routine { ... }

      Or am I missing something obvious?

      Cheers,
      Ovid

      New address of my CGI Course.

        Since BEGIN-phase processing is when subs get defined, and the processing goes top-to-bottom, put your BEGIN block after the subs.
        use warnings; use strict; BEGIN { print "Before: $_\n" for grep /f/, keys %{main::}; } sub foo { print "I am defined\n"; } BEGIN { print "After: $_\n" for grep /f/, keys %{main::}; }

        Caution: Contents may have been coded under pressure.

        You fetch the list of subs the same way the list of subs is already being fetched.

        package Limit::Traits; use POSIX ':limits_h'; use Class::Traits ...; # Fetching now will give you "USHRT_MAX" etc. # but not "foo" nor "bar" sub foo { ... } sub bar { ... }

        Later, when Limit::Traits is used, you'll want to export a bunch of subroutines so you fetch the list of all code references in that package again but don't export the ones that were already there when you fetched that same list the first time (in Class::Traits::import).

        - tye