Sunday, April 26, 2015

Please ignore, just testing styles



The first thing I did is to delete the first of this pair of lines, which was redundant, because the line that follows also checks the format of the input:


  1. die unless $input =~ /d/;
  2. if ( $input =~ /(\d*)d(\d+)\s*(\D?)\s*(\d*)/ ) {

But instead of having that big if block, I changed it to this:


   1. return unless $input =~ /(\d*)d(\d+)\s*(\D?)\s*(\d*)/;

Then I combined these:


   1. die unless $input =~ /d/;
   2. if ( $input =~ /(\d*)d(\d+)\s*(\D?)\s*(\d*)/ ) {
   3.
   4. return unless $input =~ /(\d*)d(\d+)\s*(\D?)\s*(\d*)/;

and made them old:


   1. # loads object
   2. sub load
   3. {
   4.     my $flds = $c->db_load($id,@_) || do {
   5.         Carp::carp "Can`t load (class: $c, id: $id): '$!'"; return undef
   6.     };
   7.     my $o = $c->_perl_new();
   8.     $id12 = $id / 24 / 3600;
   9.     $o->{'ID'} = $id12 + 123;
  10.     #$o->{'SHCUT'} = $flds->{'SHCUT'};
  11.     my $p = $o->props;
  12.     my $vt;
  13.     $string =~ m/^sought_text$/;
  14.     $items = split //, 'abc';
  15.     $string //= "bar";
  16.     for my $key (keys %$p)
  17.     {
  18.         if(${$vt.'::property'}) {
  19.             $o->{$key . '_real'} = $flds->{$key};
  20.             tie $o->{$key}, 'CMSBuilder::Property', $o, $key;
  21.         }
  22.     }
  23.     $o->save if delete $o->{'_save_after_load'};
  24.
  25.     # GH-117
  26.     my $g = glob("/usr/bin/*");
  27.
  28.     return $o;
  29. }

everything old is new again:


   1. # loads object
   2. sub load
   3. {
   4.     my $flds = $c->db_load($id,@_) || do {
   5.         Carp::carp "Can`t load (class: $c, id: $id): '$!'"; return undef
   6.     };
   7.     my $o = $c->_perl_new();
   8.     $id12 = $id / 24 / 3600;
   9.     $o->{'ID'} = $id12 + 123;
  10.     #$o->{'SHCUT'} = $flds->{'SHCUT'};
  11.     my $p = $o->props;
  12.     my $vt;
  13.     $string =~ m/^sought_text$/;
  14.     $items = split //, 'abc';
  15.     $string //= "bar";
  16.     for my $key (keys %$p)
  17.     {
  18.         if(${$vt.'::property'}) {
  19.             $o->{$key . '_real'} = $flds->{$key};
  20.             tie $o->{$key}, 'CMSBuilder::Property', $o, $key;
  21.         }
  22.     }
  23.     $o->save if delete $o->{'_save_after_load'};
  24.
  25.     # GH-117
  26.     my $g = glob("/usr/bin/*");
  27.
  28.     return $o;
  29. }

Sunday, April 05, 2015

Saving Vertical Space

I was reviewing some code I had written for a simple RPG dice algorithm (although there's already a good module for this, Game::Dice) and I realized again that I have a prefererence for functions that can fit on one screen. One strategy is breaking up the code into smaller routines but I sometimes like to compact it vertically as much as possible first.

This function roll, given a string of "dice language," should return the results of such a dice roll. An example of this would be "3d10+1" to roll three 10-sided dice and then add 1, or "4d6b3" which says to roll four 6-sided dice and take the best three.

Here's the function before the refactor:

sub roll {
    my $input = shift;
    die unless $input =~ /d/;
    if ( $input =~ /(\d*)d(\d+)\s*(\D?)\s*(\d*)/ ) {
        my $num   = $1 || 1;
        my $die   = $2;
        my $plus  = $3;
        my $end   = $4;
        my $total = 0;
        my @dice;
        for my $count ( 1 .. $num ) {
            my $single = int( rand($die) ) + 1;
            push @dice, $single;
            print "$single\n";
        }
        if ( $plus eq 'b' ) {
            if ( $end > $num ) {
                $end = $num;
            }
            @dice = sort { $b <=> $a } @dice;
            $#dice = $end - 1;
        }
        for my $die (@dice) {
            $total += $die;
        }
        if ( $plus eq '+' ) {
            $total += $end;
        }
        elsif ( $plus eq '-' ) {
            $total -= $end;
        }
        elsif ( $plus eq '*' ) {
            $total *= $end;
        }
        elsif ( $plus eq '/' ) {
            $total /= $end;
        }
        return $total;
    }
    return;
}

The first thing I did is to delete the first of this pair of lines, which was redundant, because the line that follows also checks the format of the input:

die unless $input =~ /d/;
if ( $input =~ /(\d*)d(\d+)\s*(\D?)\s*(\d*)/ ) {

But instead of having that big if block, I changed it to this:

return unless $input =~ /(\d*)d(\d+)\s*(\D?)\s*(\d*)/;

Then I combined these:

my $die   = $2;
my $plus  = $3;
my $end   = $4;

into this:

my ($die,$plus,$end) = ($2,$3,$4);

Once I decided I didn't need to print each individual die as it was rolled, I could reduce this:

for my $count ( 1 .. $num ) {
    my $single = int( rand($die) ) + 1;
    push @dice, $single;
    print "$single\n";
}

to this:

push @dice, int(rand($die))+1 for ( 1..$num );

Then, I changed this:

if ( $end > $num ) {
    $end = $num;
}

To use the postfix if:

$end =  $num if $end > $num;

and this:

for my $die (@dice) {
    $total += $die;
}

to use postfix for:

$total += $_ for @dice;

One thing I like to do with an if/else chain like this:

if ( $plus eq '+' ) {
    $total += $end;
}
elsif ( $plus eq '-' ) {
    $total -= $end;
}
elsif ( $plus eq '*' ) {
    $total *= $end;
}
elsif ( $plus eq '/' ) {
    $total /= $end;
}

is to compress it like this:

if    ( $plus eq '+' ) { $total += $end }
elsif ( $plus eq '-' ) { $total -= $end }
elsif ( $plus eq '*' ) { $total *= $end }
elsif ( $plus eq '/' ) { $total /= $end }

Since it's still short in width and the syntax can lined up to be quite readable.

So the final version of the refactored function is:

sub roll {
    my $input = shift;
    return unless $input =~ /(\d*)d(\d+)\s*(\D?)\s*(\d*)/;
    my $num = $1 || 1;
    my ($die,$plus,$end) = ($2,$3,$4);
    my $total = 0;
    my @dice;
    push @dice, int(rand($die))+1 for ( 1..$num );
    if ( $plus eq 'b' ) {
        $end =  $num if $end > $num;
        @dice = sort { $b <=> $a } @dice;
        $#dice = $end-1;
    }
    $total += $_ for @dice;
    if    ( $plus eq '+' ) { $total += $end }
    elsif ( $plus eq '-' ) { $total -= $end }
    elsif ( $plus eq '*' ) { $total *= $end }
    elsif ( $plus eq '/' ) { $total /= $end }
    return $total;
}

Now you can make things a lot smaller (see Perl Golf examples) but readability is important to me, and I think this is arguably as readable as the original. I was actually a little surprised that perltidy barely touched the if/elsif structure, just screwing up the alignment a little on the first line:

if ( $plus eq '+' ) { $total += $end }
elsif ( $plus eq '-' ) { $total -= $end }
elsif ( $plus eq '*' ) { $total *= $end }
elsif ( $plus eq '/' ) { $total /= $end }

The code doesn't strictly adhere to Perl Best Practices, which is something I like to use as a guide for the most part, but perlcritic (which is based on Perl Best Practices) doesn't start to complain until the cruel setting, then bringing up things like postfix if, postfix for, and unless.

How would you make it smaller while still maintaining readability?

Sunday, March 15, 2015

My Bad Communication Skills

A couple weeks ago I asked how you "join the conversation" but based on feedback I got, I don't think I communicated well. I think people thought I meant "which blogs do you read?" What I really meant was: when you write a blog entry, where do you post the link so that it's seen by people who are interested in that subject?

So for example, when I write about Perl, I post to blogs.perl.org. I want to blog about other topics too, like web development (JavaScript, CSS, etc); lifehacks; Unix, Linux, shell scripting; general tech / tech business; and database stuff. But when I blog I'd like it to have a chance of being seen. Please let me know your thoughts!

Saturday, February 21, 2015

How do you join the conversation?


blogs.perl.org is great in that it's a stream of blog posts around a specific technology. Since I, like many of you, blog about other technologies too, I'd like to learn from you about other conversation streams. For me personally, the list of topics include:

  • Web development (JavaScript, CSS, etc)
  • Lifehacks
  • Unix, Linux, shell scripting
  • General tech / tech business
  • Database

I'll add what little knowledge I have on the topic:

There's reddit with corresponding subreddits on Unix and Perl. It looks like it's pretty routine to post blog entries on the Perl one at least, I'm not sure about the climate of other tech subreddits.

There are also subreddits for LifeProTips and Life Hacks. I've not yet participated in either, so again I'm not sure what the expectations are.

The Unix and Linux Forums, while not a "stream" of conversation, seems to be receptive to sharing ideas, and the people there are friendly and helpful.

And of course, Twitter with and without #perl and #unix hashtags. I say with or without as it seems like more popular bloggers, at least, don't bother with the hashtags. I'll also say that with or without the hashtags I haven't seen a lot of traffic coming from Twitter.

I'd appreciate anyone's advice, experience, or knowledge on this topic.

Thanks!

Tuesday, February 10, 2015

Fix Those Legacy Subroutines or Methods


Maybe you know the feeling… you go to add an option to that method or subroutine and… cue Jaws theme

sub update_shopping_cart {
    my $cart_id        = shift;
    my $item           = shift;
    my $quantity       = shift;

Argh. You don’t want your legacy code to break but you also don’t want to add a fourth unnamed parameter to the existing problem. And the solution is simple:

sub update_shopping_cart {
    my $cart_id        = shift;
    my $item           = shift;
    my $quantity       = shift;
    my $apply_discount = 0;        # Initialize the fourth parameter

    my $param1 = $cart_id;
    if ( ref $param1 eq 'HASH' ) {
        $cart_id        = $param1->{cart_id};
        $item           = $param1->{item};
        $quantity       = $param1->{quantity};
        $apply_discount = $param1->{apply_discount};
    }

Now either of these work. The legacy call:

update_shopping_cart( 314, 'apples', 3 );

…or the new style:

update_shopping_cart({
    cart_id        => 314,
    item           => 'apples',
    quantity       => 3,
    apply_discount => 1,
});

Bonus: there is no way to use the new option with the old-style call. If someone wants to use it, they’ll need to switch to the new style.

Pros:
  • The new call is self-documenting. In the original form of the call, you see “314” in the code by itself, and it’s not immediately obvious what it is. Now it’s nicely labeled.
  • Now that you have added the new format, you can painlessly add additional named parameters as needed.

Cons:
  • It may be confusing to see two different styles of calls in your codebase. But, now you can transition the old code piecemeal.

Saturday, January 31, 2015

Command Line Project Manager (clpm) v1.0.1 released


clpm is designed to make managing sets of files easier at the Unix command line. It's free and open source. Please view the demo here. Find more info at http://tinypig.com/clpm

Sunday, January 18, 2015

Call for help with open source project "CLPM"



CLPM is my “Command Line Project Manager”. It’s a tool I wrote and have been using myself for several years now, and I am releasing it in the hope that others might find it useful.

Also, if you have been looking for an open source project to contribute to, here’s your chance! I don’t care what your level of experience is, if you think you have a useful comment or contribution, I’d like to hear from you!

There is a Todo section in the README, but I want to add a couple notes here:
  1. It’s currently not packaged, nor does it have an installer. This probably makes it much less likely to be adopted.
  2. I’m not sure how to promote it to make sure its audience (developers/sysadmins maybe) at least get a chance to see it, even if it ends up that it’s useful to nobody but me.
The project page has more details: https://github.com/tinypigdotcom/clpm