git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: Git.pm

From
ASAndrew Sayers <andrew-git@pileofstuff.org>
Date
May 10, 2012, 20:55 UTC
Message-ID
<4FAC2B2E.7060101@pileofstuff.org>
In-Reply-To
<CAB3zAY3VHtUobJfJ7=nSKb_6uJOXLGVHzR18qV6txPkzf54cDw@mail.gmail.com>

Try::Tiny is an increasingly standard part of Perl - for example, it's used extensively in Moose. There's a good list of arguments about why you should use it instead of eval in the Try::Tiny documentation: http://search.cpan.org/~nuffin/Try-Tiny-0.01/lib/Try/Tiny.pm

Now I've got that talking point done, here's what I really think :)

Try::Tiny is designed on the assumption that throwing and catching objects is something people should do all the time, and it can cause subtle errors that are only worth the hassle if you get a lot of benefit from doing so. It's easy enough to come up with ideas for where they might be useful, but in the real world advanced uses for exceptions are usually a sign you're doing it wrong. Three of the most common reasons for frequent/complex exceptions are handling errors further up the call stack, recovering from operations that fail, and clever error-handling.

If you want exceptions to be caught by code further up the call stack than the immediate caller, you're likely to be disappointed. This is one of the places where "separation of concerns" applies - if I use a module that uses a module that uses your module, then catching exceptions from your code will just cause my program to break when some module in the middle obscures your error by adding its own layer of error handling.

If you have an operation that really might fail, and you want to encourage most people to handle it most of the time, it's better to have a function with a meaningful name and good documentation. This puts the burden on the calling function to handle the error instead of letting them think "oh well, if it dies someone else will handle it". It also forces you to split functions along boundaries that make your code readable, instead of falling for the temptation to make something that "just works"... until it doesn't, and the maintainer has to go spelunking through code they don't know. So instead of:

   try {
       Foo::frobnicate( widgets => 3 );
   } catch {
      if (ref($_) eq 'Error::Widget') {
          die "Could not add 3 widgets";
      }
   }
It's better to ask the people using your module to write:
   my $foo = Foo->new;
   $foo->add_widgets(3) or die "could not add 3 widgets"
   $foo->frobnicate;
This is easier to document, easier to write and easier to read.

If you have an operation where calling code is supposed to do something more complicated than give up, it's better to use a callback. This gives you an opportunity to document what's needed, and to check that the calling code is doing the right thing before it's too late. So instead of:

    my $widgets = 3;
    while ( $widgets ) {
        try {
            Foo::frobnicate($widgets);
            $widgets = 0;
        } catch {
            if ( $_->{remaining_widgets} < 2 ) {
                die $_->{error};
            } elsif ( $_->{remaining_widgets} == 2 ) {
                $widgets = 0;
            }
        }
    }
It's better to ask people using your module to write:
    Foo::Frobnicate(
        widgets => 3,
        error_handler => sub {
             my ( $remaining_widgets, $error ) = @_;
             die $error if $broken_widgets < 2;
             return "give up" if $remaining_widgets == 2;
             return "continue";
        },
    );
Again, this is more readable and easier to document.

Aside from the philosophical angle, Try::Tiny is particularly hard to maintain because it looks like a language extension, but is actually just an ordinary module. The try {} and catch {} blocks are anonymous subroutines, which lead to some wonderfully unintuitive behaviour. See what you think these do, then run the code to find out:

    sub foo {
        try {
            return 1;
        }
        return 0;
    }
    sub bar {
        our @args = @_;
        our @ret;
        try {
            @ret =
                wantarray
                    ?   grep( /blah/, @args )
                    : [ grep( /blah/, @args ) ]
        };
        return @ret;
    }
    my $foo = "bar";
    sub baz {
        my $foo = "baz";
        try {
            print $foo;
        }
    }
    sub qux {
        my $ret;
        try {
            $ret = "value";
        }
        return $ret;
    }
    print foo, "\n";
    print bar( "blah", "blip" ), "\n";
    baz;
    print qux, "\n";

In short, Try::Tiny looks like a lot of gain for not much pain, but actually it's the other way around.

	- Andrew
Previous: demerphqNext: demerphq
Message 13 of 24 in “Git.pm”
  1. Subho BanerjeeApr 26, 2012
  2. Randal L. SchwartzApr 26, 2012
  3. Tim HeniganApr 26, 2012
  4. Subho BanerjeeApr 26, 2012
  5. Jonathan NiederApr 26, 2012
  6. Subho BanerjeeMay 10, 2012
  7. Jonathan NiederMay 10, 2012
  8. demerphqMay 10, 2012
  9. Subho BanerjeeMay 10, 2012
  10. demerphqMay 10, 2012
  11. Junio C HamanoMay 10, 2012
  12. demerphqMay 10, 2012
  13. Andrew SayersMay 10, 2012
  14. demerphqMay 11, 2012
  15. Randal L. SchwartzMay 11, 2012
  16. Junio C HamanoMay 11, 2012
  17. [GIT.PM 1/3] Ignore files produced from exuberant-ctagsSubho Sankar Banerjee, May 19, 2012
  18. [GIT.PM 2/3] Getting rid of throwing Error::Simple objects in favour of simple Perl scalars which can be caught in eval{} blocksSubho Sankar Banerjee, May 19, 2012
  19. Andrew SayersMay 19, 2012
  20. Subho BanerjeeMay 23, 2012
  21. Andrew SayersMay 23, 2012
  22. [GIT.PM 3/3] Perl code uses eval{}/die instead of Error::Simple and Git::Error::CommandSubho Sankar Banerjee, May 19, 2012
  23. Junio C HamanoApr 26, 2012
  24. Sam VilainApr 26, 2012

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.