Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

Use method/function signatures with Perl

by Ovid (Cardinal)
on Dec 06, 2004 at 00:40 UTC ( [id://412554]=perlmeditation: print w/replies, xml ) Need Help??

If you're familiar with modern programming languages other than Perl, you may be frustrated with Perl's relatively simple argument handling. While it's very easy to understand, it leads to a wide variety of bugs that are far less common in other languages. Consider the following example of overloading a method to be both a setter and a getter (a common idiom in Perl):

sub name { my $self = shift; if (@_) { $self->{name} = shift; return $self; } return $self->{name}; }

The intent is to allow the programmer to do this:

$person->name; # get the name $person->name("Ovid"); # set the name

Superficially, this looks fine. However, this code throws away information about the arguments which in turn leads to more bugs. What happens when the person using this code tries the following:

$person->name(qw/Publius Ovidius Naso/); # or $person->name([qw/Publius Ovidius Naso/]); # or my $name = Name->new("Publius Ovidius Naso"); $person->name($name);

With the code as written, all of those method calls would "succeed" and silently DWIDM (do what I don't mean.) This is a pernicious source of bugs. In fact, there's a lot of debate in the Perl community about whether or not it's worth putting in a lot of extra checks in the code to protect against this.

sub name { my $self = shift; if (1 == @_ && ! ref $_[0]) { $self->{name} = shift; return $self; } elsif (! @_) { return $self->{name}; } else { croak "Unknown arguments to name()"; } }

Wow. Our nice, clean, easy to read subroutine is starting to turn into an ugly mess. That's one of the reasons I've favored separate setters and getters, but what I would really like to do is this (available in just about any modern programming language):

sub name($self) { return $self->{name}; } sub name($self, $name) { $self->{name} = $name; return $self; }

Wouldn't that be nice? It would be so much easier to program that way. Well, now you can. I've uploaded Sub::Signatures to the CPAN.

It allows you to specify method and function signatures and dispatch accordingly. In 'loose' mode (the default), it merely dispatches based on the number of arguments:

use Sub::Signatures; sub foo($bar) { print $bar; } sub foo($bar, $baz) { print "$baz, $bar"; } foo(1); # prints 1 foo(1,2); # prints 1, 2 foo(1,2,3); # fatal error

In 'strict' mode, it requires that functions/methods be "typed" according to what ref would return. If ref returns nothing, then SCALAR is assumed. If not type is specified, SCALAR is assumed. That, by the way, is why the &name examples above do what I meant. In strict mode, with no type specified, the $name argument is required to be a SCALAR. Attempting to assign a Name object is a fatal error.

Currently supported features:

  • Methods
  • Subroutines
  • Optional strong typing via the ref function
  • Exporting
  • Inheritance
  • Useful error message

And in version 0.1, currently on its way to the CPAN, I've added support for anonymous functions and halting compilation for duplicate signatures.

Yes, this is alpha code and yes, it's a source filter. If your particular brand of dogmatism says "no source filters" that's fine, but do read this first. If you still don't want to use this, I won't be offended.

If you do want this, please give me feedback, suggestions, bug reports, etc. I would like to get this "production ready" if possible.

Cheers,
Ovid

New address of my CGI Course.

Replies are listed 'Best First'.
Re: Use method/function signatures with Perl
by BrowserUk (Patriarch) on Dec 06, 2004 at 04:45 UTC

    I'm not sure if this is a useful idea, but have you considered using wantarray or Want to allow dispatch on return context also?

    I was in the process of trying to hand code a multi-dispatch method for a function that takes 3 args. Two of which can be either strings, or array refs, or hash refs, or globs. The third is a simple scalar.

    The function applies the value or values (which can be a single scalar, or an array of scalars, the keys of a hash, or each of the lines from an open file handle) of the second argument, to each of values (a scalar, and array of scalars, or the keys of a hash, or lines from a file) of the first argument.

    I want to use separate subs for each of the variations for two reasons:

    1. Having conditional code in the body of the sub to handle all these variations would horrible complicate the code.
    2. It would also slow the code by a big margin for something that needs to be fast.

    I think your module would be of great value here.

    However, the way the function would report it's results would (could) also depend on the context in which it is called. In a void context, it would write the results straight to STDOUT, thereby avoiding accumulating large volumes of data in memory and passing it, or a reference to it, back to the caller to write to a file.

    If the first parameter is a hash ref, then the results of applying the function to a key, would be stored back into the hash as a value and a total count of results return in a scalar context, or a list of counts for the keys in a list context (maybe?).

    If the first parameter is an array, a reference to an array of results would be returned in a scalar context and a list of results in a list context.

    For two scalars, an array ref in a scalar context, or the results themselves in a list context.

    With Want, other useful variations would be possible (I think, I only just downloaded it a coupleof days ago). I can put code in each of the subs to make this determination, but again, it complicates and slows down the code.

    Any chance you might be able to incorporate caller context in the dispatch also?


    Examine what is said, not who speaks.
    "But you should never overestimate the ingenuity of the sceptics to come up with a counter-argument." -Myles Allen
    "Think for yourself!" - Abigail        "Time is a poor substitute for thought"--theorbtwo         "Efficiency is intelligent laziness." -David Dunham
    "Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon

      That's a fascinating thought, I must admit. I'll keep it in mind, though I'm unsure of the best approach for implementation. For the time being, you could use a wrapper subroutine that handles the context for you and have it call the "signified" subs.

      Cheers,
      Ovid

      New address of my CGI Course.

        No. I can't either. I was initially thinking that as your running at compile time, you could generate a sigified-without-context wrapper sub that detectes the context and dispatched to the sigified-with-context subs.

        And then replicate the user written sigified-without-context subs, to save coder effort and (human) cut&paste code reuse.

        Unfortunately, whilst entirely possible to do, it doen't buy you much as you end up with three version of the same code with different names.

        I tried to think of some way of using the source-filter nature of your module to perform some source-substitution "macro expansion" when generating the 3 variant requirements, but it may be too big a task I fear.

        In the simple case of don't do anything in a void context and return the list or a reference to it in the other two, it's easier, and perfectly efficient to make that determination at runtime.

        For the more complex case, the best idea I came up with is a a trio of pseudo-pseudo-blocks.

        VOID{ ... } SCALAR{ ... } LIST{ ... }

        When munging the subnames in the source filter, you'd also look for these. If the user's sub definition contained one or more of them, then you would generate the signified-without-context dispatcher sub. And one signified-with-context duplicate of the user's sub definition, but with the appropriate pseudo-block label, and the entirity of the other two types of pseudo-block removed. Or a fatal exception catcher for any that didn't exist.

        It apppealed for a while, but I'm not sure that any efficiency gains from the removal of the runtime conditionals would give wouldn't be outweighed by the extra level of dispatch. And whilst the pseudo-blocks would allow the user's source to be fairly clean if the decisions are simple, it could easily get messy if they are more complex.

        Finally, the extra complexity in the source filter would probably make test and validation much harder for production quality to be achieved.

        Oh well. It seemed like a good notion when the thought struck me.


        Examine what is said, not who speaks.
        "But you should never overestimate the ingenuity of the sceptics to come up with a counter-argument." -Myles Allen
        "Think for yourself!" - Abigail        "Time is a poor substitute for thought"--theorbtwo         "Efficiency is intelligent laziness." -David Dunham
        "Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon
Re: Use method/function signatures with Perl
by BUU (Prior) on Dec 06, 2004 at 02:09 UTC
    I realize this is kind of a broad and lazy question, but I too have heard for years and years that source filters were the greatest source of evil. Now you are apparently advocating something different, that they are useful and non-evil.

    I would love to think that they were non-evil, but the use.perl journal you refer to is sadly lacking, as it looks to me, actual facts. You just say "It worked fine for me and we only found one bug". Which sounds nice, but it sounds like a lot of other advocacy I've heard, some of which was true and some of which wasn't.

    I don't want to sound overly negative or accusatory, but I was wondering if you could provide a little more in depth explanation as to why you previously considered filters evil and why they are no longer evil?

      The answer is simple, really. Previously I disliked source filters because I knew they could be a source of serious bugs that could be difficult to find. Further, source filters can require all sorts of tweaking as we find this special case and that special case and struggle to ensure that we've covered them all.

      Now, however, Filter::Simple handles many of the "worst case" scenarios. For example, you no longer have as much concern about filtering quoted text, comments or POD. If I had to deal with that, this module would be much larger and probably would not have been written. As it stands, I merely filter the "code" portion of the target module and everything works.

      Another thing which has changed my mind is testing. I now do many things that I would never have dreamed about previously because I have the tests to immediately tell me if I've failed. I can reach for more powerful tools without worry about misusing them because of tests.

      And finally, dogmatism of any sort scares me. When someone says "never do X" I immediately want to find reasons why X might be a good thing to do. In this case, by allowign function signtures and multi-method dispatch, an entire class of bugs in Perl is almost completely eliminated. Further, this is a class of bugs that other languages generally don't have, so this puts Perl on more of an equal footing. For example, consider how the &name code would look in Java (given that we cannot exactly duplicate the behavior because it's not possible to have different return types for methods with the same name):

      public String name () { return name; } public String name(String name) { this.name = name; return name; }

      And doing that in Perl, as seen previously:

      sub name { my $self = shift; if (1 == @_ && ! ref $_[0]) { $self->{name} = shift; return $self; } elsif (! @_) { return $self->{name}; } else { croak "Unknown arguments to name()"; } }

      I thought Perl was supposed to be more concise? In cases like this, it's not. However, my code makes Perl about as concise as Java and still allows for the full power of Perl. Argument validation in Perl is so tedious that many people (including me) tend to ignore it. Sure, test suites frequently catch the resulting bugs -- but not always. Further, the edge cases get ignored. We find ourselves doing silly things like passing an array reference around until eventually someone tries to call a method on it and they have to trace through a call stack to find out where the arrayref was first passed.

      My code is an attempt to alleviate that problem and eliminate the ability to write these bugs. Safeguarding against problems is typically cheaper than fixing them. My personal experience with source filters has convinced me that, when used properly, the gains can tremendously outweigh the losses.

      Cheers,
      Ovid

      New address of my CGI Course.

      You know, I just remembered something. I happened to mention that in my entire time using the source filter at my previous place of employment, only one bug bit me. I think it's worth looking at.

      The filter allowed method signatures, but it did not do multi-method dispatch, so it was far more limited in scope than mine. One thing it did do that mine does not is create prototype stubs for the functions. So this:

      sub foo($bar) { ... }

      Became this (more or less):

      sub foo($); sub foo { my ($bar) = @_; ... }

      Unfortunately, because it's difficult to distinguish a method from a normal subroutine, it created those prototypes for methods. With methods they are a almost a no-op. One day I had a method in a class that was probably not needed as there was a suitable method in a base class. In case I was wrong, I decided to comment out the method instead of deleting it. Can you spot the resulting bug?

      sub foo($$); # sub foo($self, $bar) {...}

      This fails horribly when I do $object->foo($stuff) because Perl sees that &foo is defined in the current symbol table and then tries to call it, but since there is no subroutine, it dies with an "Undefined subroutine" warning, even though I clearly had that method in a base class.

      That was a very difficult bug to track down and it caused a lot of confusion. One would think that this merely reinforces the "source filters are always bad" camp, but in reality, this bug stemmed from our not using Filter::Simple but instead relying on the older Filter::Util::Call. Had we used Filter::Simple, we would have filtered only on code and the one bug never would have bitten us. This module saved us so much time in development, though, that the time wasted tracking down this one bug was easily offset by the benefits gained by all developers using it.

      Cheers,
      Ovid

      New address of my CGI Course.

Re: Use method/function signatures with Perl
by Juerd (Abbot) on Dec 06, 2004 at 11:40 UTC

    I think the entire idea of rvalue accessor methods is flawed, now that we have the possibility to create lvalue ones. Since years ago, I have made programming much easier for myself by inventing some very simple rules:

    • Methods should behave like subs
    • Object variables (properties in Perl 5 jargon, attributes in Perl 6 jargon) should behave like variables
    • Typing curlies sucks, as does exposing ALL internal variables.
    Why should they behave like variables? Because in Perl, variables are powerful. They are lvalues, and they can be modified efficiently without copying the values. But mostly because they ARE variables, and as much as I like using black boxes with no knowledge of the internals, I do not wish to hide that these things are variables. Hiding that takes away a lot of power that lies in how Perl's variables work. To have this power without providing an lvalue interface, you'd need to create a method for every operator and function that uses lvalues.

    Value validation used to be a good reason not to use lvalues. But there is Attribute::Property now. Not that I use it myself, though: sub foo : lvalue { shift->{foo} } (or sub foo : lvalue { $foo{+shift} } for inside-out objects) has proven to work very well, and value validation is something I don't do much (I expect people to RTFM).

    $person->name; # get the name $person->name("Ovid"); # set the name $person->name(do { (my $temp = $person->name) =~ s/Ovid/Juerd/; $temp +}); $person->name($person->name . "\n"); $person->name(do { chomp(my $temp = $person->name); $temp });
    $person->name; $person->name = "Ovid"; $person->name =~ s/Ovid/Juerd/; $person->name .= "\n"; chomp $person->name;

    This still throws values in @_ away. But as idiomatically this syntax is used without any parentheses at all, that is not a problem.

    Juerd # { site => 'juerd.nl', plp_site => 'plp.juerd.nl', do_not_use => 'spamtrap' }

      And I strongly disagree with you on the value of lvalue subroutines.

      As you indicate, you're throwing away the possibility of value validation. Whether or not you currently do any value validation, it is important to me to have the option. (If for no other reason than the fact that you can retroactively add it to help track down bugs.) Furthermore I take objection to being told that my programming practice is flawed because of a disagreement like this.

      Saying that you can use attributes to get value validation back doesn't comfort me. As I've repeatedly noted, I avoid attributes because the CHECK method may not be run for modules in my environment. So offering a buggy solution to replace a non-solution doesn't comfort me.

      I really like Ruby's compromise. You have the methods bar and bar= that can do anything they like. If they do what the names suggest then foo.bar acts like an lvalue. The language has a syntax that makes autogenerating (very effient and barebones) versions of these trivial. But you can do whatever you want.

      However in Perl I'll continue to use rvalue accessors, thankyouverymuch.

        As you indicate, you're throwing away the possibility of value validation.

        As I indicate, Attribute::Property takes that argument away. You may not like that it is implemented as an attribute, but never did I say that lvalues imply throwing away the possibility of value validation. If anything, I said that it used to be harder without A::P. As with any lvalue in Perl: you can validate it as long as you don't fear tie. For $foo this hasn't been a problem, but for $object->foo some people do object.

        package main; { my $foo; sub foo { if (@_) { my $new = shift; # validate return $foo = $value; } return $foo } }
        I have yet to see code like this. And fortunately so.

        Saying that you can use attributes to get value validation back doesn't comfort me.

        If this is indeed because of CHECK, I'll be happy to add another module, one that does the same thing without attribute syntax1. Attribute syntax isn't needed, it's just very nice syntactic sugar that doesn't require a source filter. But for some reason, I do not think this is the real reason for you not to use this lvalue properties.

        I hope that you just object to tie, or to anything that disagrees with perldoc, or to "experimental" features, because Perl 6 will assume you want lvalue accessors.

        I really like Ruby's compromise.

        So do I.

        However in Perl I'll continue to use rvalue accessors, thankyouverymuch.

        Don't let my advocacy for laziness stop you.

        Juerd # { site => 'juerd.nl', plp_site => 'plp.juerd.nl', do_not_use => 'spamtrap' }

        (Update) 1 Of course, the lvalue attribute is still needed. I'm assuming, but not entirely sure, that they're not handled at CHECK time.
Re: Use method/function signatures with Perl
by Juerd (Abbot) on Dec 06, 2004 at 12:03 UTC

    sub name { my $self = shift; if (1 == @_ && ! ref $_[0]) { $self->{name} = shift; return $self; } elsif (! @_) { return $self->{name}; } else { croak "Unknown arguments to name()"; } }

    There is a lot of readability to be gained by just writing it differently:

    sub name { my $self = shift; $self->{name} = shift, return $self if @_ == 1 croak "Unknown arguments to name" if @_; return $self->{name}; }
    Of course, because I hate methods that return $self (and methods that return something different based on the number of arguments), I would just have this instead:
    sub name { my $self = shift; $self->{name} = shift if @_ == 1 croak "Unknown arguments to name" if @_; return $self->{name}; }
    which again is easier to read and type. But this is still based on your example. For those who don't mind having the return value being consistent regardless of the number of arguments, there is an even better way to write the accessor, but then the similarity with your example is gone:
    sub name { @_ > 1 and croak "Too many arguments for name"; my $self = shift; return @_ ? $self->{name} = shift : $self->{name}; }
    (Note that I changed the error message to something that looks like what perl itself uses in such a situation.)

    But nothing beats:

    sub name : lvalue { shift->{name} }
    in writing, reading and maintaining the method, but also in writing, reading and maintaining the code that uses it.

    Juerd # { site => 'juerd.nl', plp_site => 'plp.juerd.nl', do_not_use => 'spamtrap' }

      Juerd wrote: There is a lot of readability to be gained by just writing it differently:

      sub name { my $self = shift; $self->{name} = shift, return $self if @_ == 1 croak "Unknown arguments to name" if @_; return $self->{name}; }

      Minor point: readability is often subjective. I'm not sure if your code is easier to read, but it is more concise. However, it's still not as easy to read (IMHO) is MMD:

      Major point: Perl has an entire class of bugs not found in other languages and in our debating the merits of how to deal with it, both of us have fallen victim to this bug.

      Your code has a bug. Specifically, mine does not allow a reference to be assigned to name. Your code does. This seems like a minor nit but this goes to the code of the problem I am trying to solve. Consider the following snippet from the POD of my module:

      sub name { my $self = shift; $self->set_name(@_) if @_; # I forgot the return! return $self->{name}; } sub set_name { my $self = shift; $self->{name} = shift; return $self; }

      I didn't intend to put a buggy example in my POD, but my bug and your bug are in the same class of bug. Specifically, forcing programmers to check the number and type of arguments is to shift programming burden to the programmer when it can easily be handled by the computer. I gave a live testing demo at Portland Perl Mongers and many who actually did testing admitted they skip this validation in their actual code unless the code is a critical part of the system. Why should this be done? Most languages provide this validation with signatures (though this still only validates type and not domain.)

      Cheers,
      Ovid

      New address of my CGI Course.

        Your code has a bug. Specifically, mine does not allow a reference to be assigned to name. Your code does.

        A major point indeed. I'll abuse it for my own purpose, though: it's the unreadability that made me overlook it! :)

        But the fix is simple and doesn't hurt readability much. You are right that accessor methods are error prone in multiple ways. Fortunately, simple lvalue accessor methods don't have this problem.

        Juerd # { site => 'juerd.nl', plp_site => 'plp.juerd.nl', do_not_use => 'spamtrap' }

Re: Use method/function signatures with Perl
by gaal (Parson) on Dec 06, 2004 at 07:09 UTC
    (I'm on my way to work and haven't had time to read through evertything carefully here, but there's a comment I want to make before I forget:)

      In 'strict' mode, it requires that functions/methods be "typed" according to what ref would return.

    Wouldn't it be better to run an isa check instead? This is a mistake I sometimes make, myself.

      Yes, it would be better. However, I wanted to keep the initial release as simple as possible with the intent of getting feedback and bug reports so I can see where I stand. Right now, CPAN testers shows 8 passes and I have a fair number of tests, so at least the code is working like I expected. It's when someone starts using it in a way I don't expect and it works that I'll feel happy. Later I hope to fix the isa issue.

      Cheers,
      Ovid

      New address of my CGI Course.

Re: Use method/function signatures with Perl
by Anonymous Monk on Dec 06, 2004 at 01:26 UTC

      Several things. Let's compare. Here's some code from the CMM docs:

      multimethod find => (Container, Query) => sub { $_[0]->findquery($_[1]) }; multimethod find => (Container, Sample) => sub { $_[0]->findlike($_[1]) }; multimethod find => (Index, Word) => sub { $_[0]->lookup_word($_[1]) };

      And how would that compare with Sub::Signatures?

      sub find(Container $container, Query $query) } $container->findquery($query); }; sub find(Container $container, Sample $sample) } $container->findlike($sample); }; sub find(Index $index, Word $word) { $index->lookup_word($word) }; }

      Mine is easier to use and a bit more flexible if you merely want to dispatch based on the number of arguments. It's also easier to understand because it looks like just about every other language out there as opposed to the CMM syntax. This means existing code is more easily ported to/from this solution. Also, my code (IMHO) is simpler than Damian's, though he does his without the source filter. I guess it all boils down to the fact that I've never felt comfortable with the interface to CMM.

      Also, my code is more than multi-method dispatch. Just having function signatures is a nice bonus and you can even use them with anonymous subs (in 0.1 and without the MMD.)

      In short, there's nothing wrong with Damian's solution. The major difference in code lies in my trying to solve a problem of a somewhat different scope. At this point, the only significant advantage to his code is the fact that it's been around a lot longer is likely to be more robust as a result. My code quite possibly has serious limitations that I haven't accounted for. That's why I'm looking forward to bug reports :)

      Cheers,
      Ovid

      New address of my CGI Course.

Re: Use method/function signatures with Perl
by dragonchild (Archbishop) on Dec 06, 2004 at 13:57 UTC
    I'd be interested to see
    • what kind of speed penalty your dispatching has
    • what kind of impact it has on caller() and other introspective code
    • if it correctly handles
      sub foo(CGI $cgi) { ... } sub foo(CGI $cgi, ARRAY $arr) { ... }
    A few questions:
    • Why do you use ref() (or potentially isa() ...) over Scalar::Util's blessed()? I've had serious issues when HTML::Template used isa() to determine ARRAY when I had overloaded stringification on my object. Using blessed() or can() would have solved that problem ...
    • Is the problem with more than one package in a file your problem or Filter::Simple's problem? If it's F::S's problem, you should state that as a limitation of the engine you're using.
    • Why don't you allow sub foo(@arr) { ... } instead of sub foo(ARRAY $arr) { ... }? Wouldn't that give me the prototype sub foo(@);, allowing me to rewrite push?

    And, finally ... why did you use a source filter yourself instead of using Attributes? I would have thought that Attributes would have provided a tested source filter so you could have focused on your core requirements instead of worrying about the source filter yourself.

    Being right, does not endow the right to be rude; politeness costs nothing.
    Being unknowing, is not the same as being stupid.
    Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
    Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

      There's a lot of stuff here and I can't answer it all as I get ready for work so I'll rush through this.

      The speed penalty is most at compile time. There's actually not much of a runtime speed penalty if code without this module attempts the same level of validation. If you don't mind the compile time hit, you're probably OK. In a persistent environment (such as mod_perl) you may never notice. Caveat: I haven't benchmarked this, so take what I say with a grain of salt. I was looking for programmer efficiency rather than CPU efficiency.

      It should have no impact on caller because internally it uses goto to subvert the call stack. In this respect, it's even better than some hand-rolled code. However, I didn't write tests for this. I should do that. And can you give other introspective examples you'd like to see tested?

      As for the code snippet you tested, yes it will handle that, if you use 'strict' mode.

      use Sub::Signatures 'strict'; sub foo (CGI $cgi) {...} sub foo (CGI $cgi, ARRAY $arr) {...}

      In 'strict' mode, it considers the types of variables. That would actually work in 'loose' mode, but only because each subroutine has a different number of variables. Naturally, that would be more bug-prone. I wonder if I should have made 'strict' the default instead of 'loose'?

      I used ref instead of Scalar::Util on the "simplest thing that could possibly work" principle. If you can give me a clear code snippet showing why ref is inferior (or point me to a resource.) I'll happily change it. (Update: Of course, I seem to recall some of the issues you mention. Hmm, I don't think I have a choice but to change it.)

      The problem with more than one package per file is a combination of my code and how Filter::Simple works. It's very important that I know my calling package when setting argument lists with the subroutines so I determine this in &import. However, Filter::Simple keeps scanning through the code and cheerfully skips past package declarations, thus meaning I could alter subs in the wrong package. I thought about trying to parse out the the declarations and it didn't seem too hard, but Perl has so many odd corners that I thought there would be a good chance of missing something. As a result, I opted to keep it simple for my initial release.

      I don't use prototypes because they're useless with methods and I wanted to limit the differences between using functions and methods. Right now they behave almost identically. What I didn't want was having to constantly respond to the following bug report:

      sub foo(@bar) {...} # works fine sub foo($self, @bar) {...} # how would this work?

      And I didn't use attributes because even though I knew I could get something like signatures working, the real problem I wanted to transparently solve was signature-based multi-method dispatch. I don't know if that's possible with using attributes and a relatively straightforward syntax.

      Cheers,
      Ovid

      New address of my CGI Course.

        I used ref instead of Scalar::Util on the "simplest thing that could possibly work" principle. If you can give me a clear code snippet showing why ref is inferior (or point me to a resource.) I'll happily change it. (Update: Of course, I seem to recall some of the issues you mention. Hmm, I don't think I have a choice but to change it.)

        Another question here - are you planning on allowing objects that happen to be implemented as ARRAY's in for ARRAY parameters? This will mean that your code is going to be ... complex, to say the least.

        Being right, does not endow the right to be rude; politeness costs nothing.
        Being unknowing, is not the same as being stupid.
        Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
        Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

Re: Use method/function signatures with Perl
by rinceWind (Monsignor) on Dec 06, 2004 at 13:09 UTC
    With the code as written, all of those method calls would "succeed" and silently DWIDM (do what I don't mean.) This is a pernicious source of bugs. In fact, there's a lot of debate in the Perl community about whether or not it's worth putting in a lot of extra checks in the code to protect against this.
    This is one of the reasons I like Params::Validate. You can put as much or as little validation rules as you want.

    --
    I'm Not Just Another Perl Hacker

Re: Use method/function signatures with Perl
by demerphq (Chancellor) on Dec 06, 2004 at 17:17 UTC

    IMO stuff like this quickly leads to a cognitive mismatch between their author and the bulk of the perl community. For instance passing more args to a subroutine than it needs should IMO not be viewed as a bug. Its behaviour that many a perl programmer would consider completely normal.

    I think people tend to make a big deal out of parameter passing in Perl when really they should remember the old dictum: "programs should be as tolerant as possible in what they accept and strict as possible in what they emit." Id say that applies equally well to subroutines.

    Anyway, im not trying to say that your module cant be useful, im sure it will be. But people using it should be reminded that the programs they produce using it wont behave as many experienced perl programmers would expect.

    ---
    demerphq

      I'd wager that at least 95% of the time, passing more arguments into a subroutine than it expects is a bug.
        I'd take that wager. 95% of the time, passing more arguments into a subroutine than it expects is a sign of forwards- (and sometimes backwards- ) compatability. Other times, it's a sign of API compatability. For example, I've got a templating module that works with more than one rendering engine. There's a given function that both engines support. But, one takes (and handles) a foo parameter and the other doesn't. If I cannot pass the foo parameter in (to be silently ignored if necessary), that's a sign of bad assumptions.

        Be liberal in what you accept and strict in what you emit.

        Being right, does not endow the right to be rude; politeness costs nothing.
        Being unknowing, is not the same as being stupid.
        Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
        Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

      For instance passing more args to a subroutine than it needs should IMO not be viewed as a bug. Its behaviour that many a perl programmer would consider completely normal.

      We've touched this discussion before (Re: Re: Re: Context aware functions - best practices?). I do not agree. There is no concensus on what will happen with the extra arguments. I'm a firm believer of that documentation is what decides what should be considered a bug or not. If the documentation says a subroutine takes three arguments then I read that as the subroutine should be given three arguments. I don't read it as the subroutine should be given minimum three arguments.

      If everyone would live by "it's OK to pass too many arguments" then there would be a lot of unnecessary incompatible updates (i.e. when the module author adds an argument to the subroutine interface). If everyone would live by "it's not OK to pass too many arguments" then module authors wouldn't have to worry about extending the subroutine interfaces and the number of incompatible updates would be reduced.

      Assuming that passing too many arguments is silently ignored is to assume things about the implementation, and assuming things about the implementation is by general concensus evil.

      Just because it works now doesn't mean it'll work tomorrow--unless documented to do so.

      ihb

      See perltoc if you don't know which perldoc to read!
      Read argumentation in its context!

Re: Use method/function signatures with Perl
by Anonymous Monk on Dec 06, 2004 at 17:13 UTC
    Let's not confuse the want of better parameter checking with getter/setter duplication. Yes, combining the two is (IMHO) bad form, but in general I write OO without getter/setters anyhow, since this is sort of exposing implementation details and it shouldn't be done that way most of the time.

    Ruby gets along fine with duck typing. I'm really not concerned there, and I'm not sure perl should be either. I guess the thing is when something is method-oriented, you'll run across the equivalent runtime error a bit sooner, as with Perl you have a good chance of only getting something like "not a HASH at (xxxxxx)".

    The advantage of duck typing is that if you suddenly decide that your FrogBucket needs to accept Weasels, all you need to do is make your Weasels know how to quack().

      Actually, the better parameter checking is merely to enforce traditional multi-method dispatch. You have a great point about Ruby's duck typing. If you don't care about types, just don't use Sub::Signatures 'strict' mode and dispatch will be based on the number of arguments and type will be ignored.

      Cheers,
      Ovid

      New address of my CGI Course.

Re: Use method/function signatures with Perl
by radiantmatrix (Parson) on Dec 06, 2004 at 17:09 UTC

    This is kind of cool, but I've been severely bitten by source filters in the past, and continue to have a number of personal stylistic issues with using them. What I'd like to see (and if someone knows of a CPAN module that does this, I'd be grateful) is something that would allow subs to be written thusly:

    use Verifier; my $verify = new Verifier (on_failure => sub { die(join (',',@_)) }) sub foobar { ## The '-' in front of hashref means $bar is an optional paramater my ($foo, $bar) = $verify->type ( \@_, qw/scalar -hashref/); print "Using $foo:\n"; return $foo unless %$bar; for (keys %$bar) { print "$_ => $$bar{$_}\n" } return $$bar{'result'}; }

    That is, I'd really like to see verification of parameter types handled by a verifier object rather than by a source filter. Yes, I know it's not quite the same thing; but by eval'ing the type verifier call, one could achieve the same basic functionality.

    Update: It turns out, thanks to a mention by dragonchild and xdg, that Params::Validate does just the above. An example of the above code, but using that CPAN module, can be found a bit further down in this discussion.

    radiantmatrix
    require General::Disclaimer;
    s//2fde04abe76c036c9074586c1/; while(m/(.)/g){print substr(' ,JPacehklnorstu',hex($1),1)}

      What's wrong with how Params::Validate does things?

      Being right, does not endow the right to be rude; politeness costs nothing.
      Being unknowing, is not the same as being stupid.
      Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
      Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

        Aha. I knew someone must have had the same general idea before! Thank you for that link. I only have a tiny gripe with it vs. my ideal verifier, which is that Params::Validate only validates, where as what I wrote above would imply validation and returning of the validated parameters (rolling two operations into one).

        I'm certainly not about to reinvent a wheel over something so minor, though. The module you recommended will work nicely. Thanks again!

        Update: this is what I get for merely scanning the POD before posting. As xdg points out, Params::Validate does everything I have wished for:

        use Params::Validate; sub foobar { my ($foo, $bar) = validate_pos ( @_, {type = SCALAR, regex=>/.+/}, {type = HASH_REF} ); print "Using $foo:\n"; return $foo unless %$bar; for (keys %$bar) { print "$_ => $$bar{$_}\n" } return $$bar{'result'}; }

        is equivalent to my earlier code.

        radiantmatrix
        require General::Disclaimer;
        s//2fde04abe76c036c9074586c1/; while(m/(.)/g){print substr(' ,JPacehklnorstu',hex($1),1)}

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://412554]
Approved by atcroft
Front-paged by astaines
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others browsing the Monastery: (3)
As of 2024-03-19 05:17 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found