perl context madness (ie- mad computer science)

Non-Disc Golf Stuff

Moderators: Timko, Solty, Frank Delicious, Blake_T, Fritz, Booter

perl context madness (ie- mad computer science)

Postby SkaBob » Thu Dec 17, 2009 3:39 pm

I had to share this with someone, and I hate perlmonks, so since SOMEONE on here will probably understand me, you guys get to hear it...

So I'm writing a CGI script that's a form blah blah...pretty standard stuff.

I'm not using strict (yes, I know, but strict breaks the rest of the site, and I don't have the authority to choke the dude that made it that way), so I assume that's the cause of this.

Now, CGI.pm allows you to creat drop-down lists via the popup_menu() function, and specify the values with an array, and the labels of those values with a hash. The hash should have the same keys as the values in the array, and the labels you want to display should be the values of those keys.

Thus:
Code: Select all
@array = [0, 1, 2, 3];
%hash->{0} = 'foo';
%hash->{1} = 'bar';
%hash->{2} = 'baz';
%hash->{3} = 'bang';

(yes, I know that's long form on the hash, but it's more readable)

So the menu has the values of 0, 1, 2 and 3, and shows the text of the options to be foo, bar, baz and bang.

OK...Here's where we enter the twilight zone.

I wrote subs to generate those arrays and hashes for all the drop-downs on the form, and call them thusly (names have been changed because their purpose makes them look like gibberish):
Code: Select all
@menu1_list, %menu1_labels = makeMenu1();
@menu2_list, %menu2_labels = makeMenu2();

and so on...

And the functions return thusly:
Code: Select all
return @menu1_list, %menu1_labels;
return @menu2_list, %menu2_labels;

and so on...

I have 6 such menus. 5 of them worked exactly as intended, displaying properly on the page and submitting the right values.

The 5th of the 6 in order of operations...well...let's say it's the retarded step-cousin or something.

The dataset it's query returns is:
Code: Select all
ID     | Last_Name     | First_Name
----------------------------------
1      | Foo               | Bar
2      | Baz               | Bang


I pushed the value 0 into the array beforehand, and assigned the value to key 0 in the hash as "Choose One" - so there'd be a default I could validate on instead of forcing what could potentially be an incorrect value on submission.

So, the hash SHOULD look like this (again in long form for readability) once it's been fed by the sub:
Code: Select all
%hash->{0} = 'Choose One';
%hash->{1} = 'Foo, Bar';
%hash->{2} = 'Baz, Bang';


Except, it instead looked like:
Code: Select all
%hash->{0} = 1;
%hash->{1} = 1;
%hash->{2} = 2;
%hash->{'Choose One'} = undef;


The code for each of the 6 drop-down menus is IDENTICAL. I wrote one, it worked, and I copied and pasted it 5 times to make the other subs. The ONLY differences were variable names (all of which I checked, and even changed to see if some naming convention was hitting a builtin or something), and the field the query checks in it's where clause (irrelevant to such a problem).

Now...hopefully someone sees WHY this happened already (if so, PLEASE enlighten me as to why it behaved as it did, and only for ONE of the six subs), if not...remember this is context madness, and I'm not using strict.

So, when I assigned the variables up top when calling the sub, it was meddling with the already assigned values that the sub was generating - because I was a sillyhead and called them the same thing in both - after finding out that if they WEREN'T named the same, the variables wouldn't have values when it came time to render the HTML.

How did I fix it? Well...thanfully since it's not using strict, I don't have to worry about the values evaporating after the sub completed...

Code: Select all
Before:
@menu1_list, %menu1_labels = makeMenu1();

After:
makeMenu1();


Works with or without return values specified in the sub.

The other ones work either way, the 5th one in the chain only works the latter way...

but....WHY?!
I threw Wizards before they were cool.
SkaBob
Disc Whore
 
Posts: 3493
Joined: Fri Mar 16, 2007 9:51 am
Location: Detroit
Favorite Disc: Comet

Re: perl context madness (ie- mad computer science)

Postby chiggins » Thu Dec 17, 2009 5:10 pm

I need to see more of the code, especially the one that makes menus. But if those code samples are verbatim, there's problems in it. First off: when returning an array and a hash like that, you gotta return 'em as refs, or they come back as a single list (double arrows in hash assignments are the same as commas for all intents and purposes). So for instance:

Code: Select all
@foo = ('one', 'two', 'three');
%bar = (4 => 'four',  5 => 'five');
return @foo, %bar;


is exactly the same as:

Code: Select all
return ('one', 'two', 'three', 4, 'four',  5, 'five');


whereas:

Code: Select all
@foo = ('one', 'two', 'three');
%bar = (4 => 'four',  5 => 'five');
return \@foo, \%bar;


returns:

Code: Select all
[ ['one', 'two', 'three'], { 4 => 'four',  5 => 'five'} ];


I don't know if that's at all helpful. Post the whole makeMenu() routine.
chiggins
2009 DGR Donator
User avatar
 
Posts: 751
Joined: Mon Apr 27, 2009 8:51 am

Re: perl context madness (ie- mad computer science)

Postby chiggins » Thu Dec 17, 2009 5:23 pm

Code: Select all
%hash->{0} = 'Choose One';
%hash->{1} = 'Foo, Bar';
%hash->{2} = 'Baz, Bang';


Except, it instead looked like:

Code: Select all
%hash->{0} = 1;
%hash->{1} = 1;
%hash->{2} = 2;
%hash->{'Choose One'} = undef;



Okay, working backwards...

Code: Select all
# wanted:
$hash = {
  0 => 'Choose One',
  1 => 'Foo, Bar',
  2 => 'Baz, Bang'
};

# got:
$hash = {
  0 => 1,
  1 => 1,
  2 => 2,
  'Choose One' => undef;
};


There's a couple things I'd be looking for immediately, the first would be something like this:

Code: Select all
@some_array = ("do", "re");
$hash->{2} = @some_array;


which would actually evaluate to this:

Code: Select all
@some_array = ("do", "re");
[b]$hash->{2} = scalar(@some_array);[/b]


That happens pretty frequently. The other thing I'd be doing would be using Data::Dumper to dump structures before they come out of their routines so I knew for sure what was being passed from place to place.

Of course, the first thing I'd do is 'use strict', 'cause that catches all this stuff :)
chiggins
2009 DGR Donator
User avatar
 
Posts: 751
Joined: Mon Apr 27, 2009 8:51 am

Re: perl context madness (ie- mad computer science)

Postby chiggins » Thu Dec 17, 2009 5:27 pm

Wait a sec:

How did I fix it? Well...thanfully since it's not using strict, I don't have to worry about the values evaporating after the sub completed...


You're using subs to assign directly to globals? I really wanna see this script.
chiggins
2009 DGR Donator
User avatar
 
Posts: 751
Joined: Mon Apr 27, 2009 8:51 am

Re: perl context madness (ie- mad computer science)

Postby Solty » Thu Dec 17, 2009 6:19 pm

man...i usta love programming.....sigh...
Image
Solty
Site Admin - Behind The Scenes
User avatar
 
Posts: 3455
Joined: Tue Nov 01, 2005 6:21 pm
Location: NE PA
Favorite Disc: Rancho Roc

Re: perl context madness (ie- mad computer science)

Postby SkaBob » Thu Dec 17, 2009 9:54 pm

chiggins wrote:Wait a sec:

How did I fix it? Well...thanfully since it's not using strict, I don't have to worry about the values evaporating after the sub completed...


You're using subs to assign directly to globals? I really wanna see this script.


I didn't WANT to. Initially it was like my examples where I was assigning the variables from the output of the sub, and the sub was returning the array and hash separately. If I'd have written everything using strict, it would most likely have worked properly. Unfortunately, some of the libs I have to include to establish our site's database connection die HORRIBLY when using strict, so the unintended side-effect is any variable assigned a value in a sub exists in a global scope.

The actual code goes something like this (from memory, since I'm at home now):
Code: Select all
sub makeMenu1() {
  $sql_menu1 = 'SELECT ID, Last_Name, First_Name FROM table WHERE bitToTest = 1 AND Active = 1 ORDER BY Last_Name, First_Name';
  $sth_menu1 = $dbh->prepare($sql_menu1);
  $rc_menu1 = $sth_menu1->execute();
  push(@menu1_list, 0);
  %menu1_labels->{0} = 'Choose One';
  while ($row_menu1 = $sth_menu1->fetchrow_hashref()) {
    push(@menu1_list, $row_menu1->{ID};
    %menu1_labels->{$row_menu1->{ID}} = $row_menu1->{Last_Name}.', '.$row_menu1->{First_Name};
  }
  return @menu1_list, %menu1_labels;
}


and it was called like so:
Code: Select all
@menu1_list, %menu1_labels = makeMenu1();


Which worked in 5 of the 6 instances, where the only changes were the variable names (eg- $sql_menu2, $sql_menu3, etc. rather than $sql_menu1), and the bit field checked in the query (bitToTest in my example).

The 5th of the 6 instances of that same code block was the one blowing up. The structure I pasted with the numbers all wrong and the undef value of a key that was assigned as a value before the loop was based on the Data Dumper output of the hash that was returned.

what WORKED was to modify the code as such:
Code: Select all
sub makeMenu1() {
  $sql_menu1 = 'SELECT ID, Last_Name, First_Name FROM table WHERE bitToTest = 1 AND Active = 1 ORDER BY Last_Name, First_Name';
  $sth_menu1 = $dbh->prepare($sql_menu1);
  $rc_menu1 = $sth_menu1->execute();
  push(@menu1_list, 0);
  %menu1_labels->{0} = 'Choose One';
  while ($row_menu1 = $sth_menu1->fetchrow_hashref()) {
    push(@menu1_list, $row_menu1->{ID};
    %menu1_labels->{$row_menu1->{ID}} = $row_menu1->{Last_Name}.', '.$row_menu1->{First_Name};
  }
}


and call it like this:
Code: Select all
makeMenu1();


Rather than assigning values from the return values of the function, the @menu1_list and %menu1_labels created in the sub are accessible anywhere in the script because the scope of their existence isn't being limited by "use strict;".

The code that makes use of the values once generated is something like this:
Code: Select all
popup_menu(
  -name => 'menu1',
  -id  => 'menu1',
  -values => \@menu1_list,
  -labels => \%menu1_labels,
  -default => (length($menu1_value)) ? $menu1_value : 0,
)


Here's what puzzles me about this.

1. 5 instances work, 1 does not, and it's not the final one so it's not like something went awry and all execution after it was insane.
2. With my previous method of doing it, even if the scope was stupid, the variables should have still gotten the same values as the return values from the function would merely clobber the array and hash with the values they already had, not horribly deformed the hash.
3. The deformation of the hash was just bizarre. There's no reason the value for key '0' should have gotten clobbered with ANYTHING, because there's no ID of 0 in that table, it's an autonumber field that starts counting at 1. So at the very least key '0' with value 'Choose One' should always remain in-tact. However, after the loop key '0' has the value of the first row's ID column, and the value 'Choose One' has become a key, with an undefined value. There's nothing that should change the value to a key, and there's nothing that should assign ANY of the ID's to a value. They should always be a key in the hash. It's like it got tossed into one of those bingo ball tumblers and came out with a random structure (though consistent through all runs of the script). And yet, like I said in point 1, all 5 other instances of the exact same code fail to do this no matter which method I used to call them or assign the values, and none of the other scripts I've used this exact same method in have ever done this - and I've used this to generate drop-down menus in CGI.pm dozens of times at this point.

I can see why, from your example it should've returned something silly since I wasn't returning refs to the array and hash, but why wouldn't it behave in that manner consistently? Shouldn't all 6 of the subs spit out deformed data if that was the case?
I threw Wizards before they were cool.
SkaBob
Disc Whore
 
Posts: 3493
Joined: Fri Mar 16, 2007 9:51 am
Location: Detroit
Favorite Disc: Comet

Re: perl context madness (ie- mad computer science)

Postby chiggins » Fri Dec 18, 2009 5:45 am

Okidoke, I set up a test table and script to get it working:

Code: Select all
#!/usr/bin/perl                                                                                                                                                                                                                             

use strict;
use DBI;
use Data::Dumper;
our $dbh = DBI->connect('dbi:mysql:test_skabob', 'test', '********');

#                                                                                                                                                                                                                                           
# here's the table:                                                                                                                                                                                                                         
#                                                                                                                                                                                                                                           
# mysql> select * from users;                                                                                                                                                                                                               
# +----+------------+-----------+--------+-----------+                                                                                                                                                                                       
# | ID | First_Name | Last_Name | Active | bitToTest |                                                                                                                                                                                       
# +----+------------+-----------+--------+-----------+                                                                                                                                                                                       
# |  1 | Alice      | Apples    |      1 |         1 |                                                                                                                                                                                       
# |  2 | Bob        | Bitchen   |      1 |         1 |                                                                                                                                                                                       
# |  3 | Clyde      | Conner    |      1 |         1 |                                                                                                                                                                                       
# +----+------------+-----------+--------+-----------+                                                                                                                                                                                       
#                                                                                                                                                                                                                                           

my ($menu_list_1, $menu_labels_1) = makeMenu('SELECT ID, Last_Name, First_Name FROM users WHERE bitToTest = 1 AND Active = 1 ORDER BY Last_Name, First_Name');
print Data::Dumper::Dumper($menu_list_1, $menu_labels_1);

sub makeMenu() {
    my $sql_menu = shift;
    my $sth_menu = $dbh->prepare($sql_menu);
    my $rc_menu = $sth_menu->execute();
    my $menu_list = [];
    push @$menu_list, '0';
    my $menu_labels = {};
    $menu_labels->{0} = 'Choose One';

    while ( my $row_menu = $sth_menu->fetchrow_hashref() ) {
        push @$menu_list, $row_menu->{ID};
        $menu_labels->{ $row_menu->{ID} } = $row_menu->{Last_Name}.', '.$row_menu->{First_Name};
    }

    return $menu_list, $menu_labels;
}


And it returns this:

Code: Select all
perl ./test_skabob.pl
$VAR1 = [
          '0',
          '1',
          '2',
          '3'
        ];
$VAR2 = {
          '1' => 'Apples, Alice',
          '3' => 'Conner, Clyde',
          '0' => 'Choose One',
          '2' => 'Bitchen, Bob'
        };


Changed things that stood out:

1. when you're inside a routine, if you declare the variables with 'my', they will be lexically scoped to that routine (whether you're using strict or not). Otherwise, they'll be global, and that will trip you up somewhere along the line.
2. use references for hashes and arrays, especially when returning them, to make sure they don't blend together. Perl will turn anything it can into a list.
3. use one routine to make your menus, pass in arguments. That way, if it starts acting funny on you in one case, you only have to debug in one place.

Check that against what you have, maybe there's something there that will help. If not, PM me and I'd be happy to take a look at the whole script.

Good hunting!
chiggins
2009 DGR Donator
User avatar
 
Posts: 751
Joined: Mon Apr 27, 2009 8:51 am

Re: perl context madness (ie- mad computer science)

Postby chiggins » Fri Dec 18, 2009 5:52 am

Oh, duh. Also, you could generalize makeMenu() a bit more by passing in a callback to make the label:

chiggins wrote:
Code: Select all
my $menu_sql_1 = 'SELECT ID, Last_Name, First_Name FROM users WHERE bitToTest = 1 AND Active = 1 ORDER BY Last_Name, First_Name';
my $menu_label_callback_1 = sub { my $row = shift; return $row->{Last_Name} . ', ' . $row->{First_Name} };
my ($menu_list_1, $menu_labels_1) = makeMenu($menu_sql_1, menu_label_callback_1);
print Data::Dumper::Dumper($menu_list_1, $menu_labels_1);

sub makeMenu() {
    my $sql_menu = shift;
    my $label_callback = shift;
    my $sth_menu = $dbh->prepare($sql_menu);
    my $rc_menu = $sth_menu->execute();
    my $menu_list = [];
    push @$menu_list, '0';
    my $menu_labels = {};
    $menu_labels->{0} = 'Choose One';

    while ( my $row_menu = $sth_menu->fetchrow_hashref() ) {
        push @$menu_list, $row_menu->{ID};
        $menu_labels->{ $row_menu->{ID} } = $label_callback->($row_menu);
    }

    return $menu_list, $menu_labels;
}



That'd be general enough assuming all your tables use "ID" for a primary key, or you could get fancier and shmancier and use a DBI method (or the particular DBD module your using) to find out the name of the table's primary key, etc etc.
chiggins
2009 DGR Donator
User avatar
 
Posts: 751
Joined: Mon Apr 27, 2009 8:51 am

Re: perl context madness (ie- mad computer science)

Postby SkaBob » Fri Dec 18, 2009 1:10 pm

grrrrrrrrrrr


ok...following your advice, I changed things around:

This calls the function, assigning the values from the output:
Code: Select all
my (@vendorservice_list, %vendorservice_labels) = getNetuserNames('bitVendorServices');

(I'm at work, so you get the actual column/var names this time)

This is the generic function I made to build all of the lists of names:
Code: Select all
sub getNetuserNames() {
  my $check_column = shift;
  my (@output_list, %output_labels);
  push(@output_list, 0);
  %output_labels->{0} = 'Choose One';
  my $sql_names = 'SELECT ID, Last_Name, First_Name FROM NETUSER WHERE '.$check_column.' = 1 AND Active = 1 ORDER BY Last_Name, First_Name';
  my $sth_names = $dbh->prepare($sql_names);
  my $rc_names = $sth_names->execute();
  while (my $row_names = $sth_names->fetchrow_hashref()) {
    push(@output_list, $row_names->{ID});
    %output_labels->{$row_names->{ID}} = $row_names->{Last_Name}.', '.$row_names->{First_Name};
  }
  return (\@output_list, \%output_labels);
}


and this is the data dumper output (it's in htmlspace, so it's not formatted like it would be on the shell):
Code: Select all
# dumper calls
print Dumper @vendorservice_list;
print Dumper %vendorservice_labels;

#output
$VAR1 = [ 0 ]; $VAR2 = { '0' => 'Choose One' };


Here's what values make it to the call to popup_menu():
Code: Select all
<select id="vendorServices" name="vendorServices">
    <option value="ARRAY(0x172b280)">ARRAY(0x172b280)</option>
    <option value="HASH(0x172b298)">HASH(0x172b298)</option>
</select>


So...the values that make it into the array and hash for use in the select CONTAIN the structures they should BE...

What I do wrong?
I threw Wizards before they were cool.
SkaBob
Disc Whore
 
Posts: 3493
Joined: Fri Mar 16, 2007 9:51 am
Location: Detroit
Favorite Disc: Comet

Re: perl context madness (ie- mad computer science)

Postby chiggins » Fri Dec 18, 2009 2:05 pm

Ah! Sigils!

Arrays and hashes are assigned into vars with @'s and %'s, whereas their references get assigned to scalars, and you de-reference them with the arrow. So, for example:

Code: Select all

# this is an array, and how to access it:
my @array = (1,2,3);
if ( $array[1] == 2 ) { ... }

# this is an array ref and how to access it:
my $array_ref = [ 1,2,3 ];
if ( $array_ref->[1] == 2 ) { ... }

# this is a hash and access example:
my %hash = ( 'one' => 'uno', 'two => 'dos' );
if ( $hash{'one'} eq 'uno' ) {...}

# this is a hash and access example:
my $hash = { 'one' => 'uno', 'two => 'dos' };
if ( $hash->{'one'} eq 'uno' ) {...}


So, going back to your code, I'm going to fix the %output_labels->{} notation and make it $output_label{}, but the most important part is changing this:

Code: Select all
my (@vendorservice_list, %vendorservice_labels) = getNetuserNames('bitVendorServices');


to this:

Code: Select all
my ($vendorservice_list, $vendorservice_labels) = getNetuserNames('bitVendorServices');


What you were doing was returning two references (stored in scalars), and assigning them as elements into an array and a hash (in fact, the hash reference you passed back might have become a key of %vendorservice_labels, which is always wierd). I'm pretty sure that popup_menu() takes those args as references, so you should be able to pass them in as:

Code: Select all
popup_menu(-name=>  'vendorservice',
                           -values=> $vendorservice_list,
                           -labels=>  $vendorservice_labels,
                          );


(Just to make sure you're passing out what you think you're passing out, put this in just before you return the outputs:)

Code: Select all
print Data::Dumper::Dumper( \@output_list, \%output_labels );
return (\@output_list, \%output_labels);


Lemme know how that goes. Here's the all of it:

Code: Select all
my ($vendorservice_list, $vendorservice_labels) = getNetuserNames('bitVendorServices');

# ...later that day...

sub getNetuserNames() {
  my $check_column = shift;
  my (@output_list, %output_labels);
  push(@output_list, 0);
  $output_labels->{0} = 'Choose One';
  my $sql_names = 'SELECT ID, Last_Name, First_Name FROM NETUSER WHERE '.$check_column.' = 1 AND Active = 1 ORDER BY Last_Name, First_Name';
  my $sth_names = $dbh->prepare($sql_names);
  my $rc_names = $sth_names->execute();
  while (my $row_names = $sth_names->fetchrow_hashref()) {
    push(@output_list, $row_names->{ID});
    $output_labels{ $row_names->{ID} } = $row_names->{Last_Name}.', '.$row_names->{First_Name};
  }
  print STDERR "Before returning from getNetuserNames: \n" . Data::Dumper::Dumper( \@output_list, \%output_labels );
  return (\@output_list, \%output_labels);
}
chiggins
2009 DGR Donator
User avatar
 
Posts: 751
Joined: Mon Apr 27, 2009 8:51 am

Re: perl context madness (ie- mad computer science)

Postby SkaBob » Fri Dec 18, 2009 2:46 pm

ah HA!

Yes, that seems to have it shiny!

Thank you!

I STILL want to know why only the ONE instance got mangled...but that's at least got me moving forward again!
I threw Wizards before they were cool.
SkaBob
Disc Whore
 
Posts: 3493
Joined: Fri Mar 16, 2007 9:51 am
Location: Detroit
Favorite Disc: Comet

Re: perl context madness (ie- mad computer science)

Postby chiggins » Fri Dec 18, 2009 2:56 pm

SkaBob wrote:ah HA!

Yes, that seems to have it shiny!

Thank you!


Awesome! Glad it's workin'.

SkaBob wrote:I STILL want to know why only the ONE instance got mangled...but that's at least got me moving forward again!


You don't get to know that. It's a gift and a curse.
chiggins
2009 DGR Donator
User avatar
 
Posts: 751
Joined: Mon Apr 27, 2009 8:51 am

Re: perl context madness (ie- mad computer science)

Postby SkaBob » Fri Dec 18, 2009 3:02 pm

Curse Perl's sudden yet inevitable betrayal! :evil:

:lol:
I threw Wizards before they were cool.
SkaBob
Disc Whore
 
Posts: 3493
Joined: Fri Mar 16, 2007 9:51 am
Location: Detroit
Favorite Disc: Comet


Return to Off-Topic, Miscellaneous, etc.

Who is online

Users browsing this forum: Bing [Bot], Yahoo [Bot] and 1 guest

cron