how to force scalar context

S

Stuart Kendrick

hi,

i'm realizing that i don't understand the distinction between list and
scalar context as thoroughly as i would like. in the examples below,
Program 1.0 delivers a different $answer than do Programs 2.x [and
Programs 2.x all deliver the same $answer.]


in Program 2.x, i understand that i'm using the "for" operator as an
alias for the "foreach" operator ... and that "foreach" surrounds
$element with a list context (whereas, in Program 1.0, $element is
surrounded in a scalar context.) i can vaguely see that perhaps
complex_function knows the context surrounding $element ... and that
it produces different results, depending on $element's context.
[complex_function produces a reference to an array containing a
blessed reference to a four element array ... that's what $answer
becomes. it is a wrapper around the snmpbulkwalk command within the
SNMP module.]

i don't understand why complex_function cares ... i wrote
complex_function, after all. but hey, i'm willing to buy the idea
that it does. so now, i want to force Program 2.0 to place $element
in a scalar context, rather than in a list context. because it turns
out that when complex_function runs in Programs 2.x, i don't get the
output i want; whereas when complex_function runs in Program 1.0, i
get my desired output.

but i don't know how to do that, at least not successfully. you can
see my various, unsuccessful, efforts in Programs 2.1 - 2.n.

insights appreciated.

--sk

Stuart Kendrick
FHCRC


Program 1.0:
$element = 5;
$answer = complex_function($element);


Program 2.0:
$array[0] = 5;
for $element (@array) {
$answer = complex_function($element);
}

Program 2.1:
$array[0] = 5;
for $element (@array) {
$element = 5;
$answer = complex_function($element);
}

Program 2.2:
$array[0] = 5;
for $element (@array) {
$element = scalar $element;
$answer = complex_function($element);
}

Program 2.3:
$array[0] = 5;
for $element (@array) {
$answer = complex_function(scalar $element);
}
 
P

Paul Lalli

i'm realizing that i don't understand the distinction between list and
scalar context as thoroughly as i would like. in the examples below,
Program 1.0 delivers a different $answer than do Programs 2.x [and
Programs 2.x all deliver the same $answer.]

Perhaps you should show us complex_function, because every one of those
programs should be doing the same thing.
in Program 2.x, i understand that i'm using the "for" operator as an
alias for the "foreach" operator ... and that "foreach" surrounds
$element with a list context

No, it does not. for/foreach imposes a list context on its list argument
- the thing in parentheses. The syntax of foreach is:
foreach SCALAR (LIST) { }
it assigns SCALAR to be an alias to each element of LIST, one per
iteration. SCALAR is just that, a scalar value.

insights appreciated.

My initial guess - and this is a large guess because you haven't shown us
your code - is that you're being caught by aliases and global variables,
not by scalar/list context. In a foreach loop, $element is an alias to
the element in @array. That is, changes to $element affect @array. Also,
in a subroutine call, @_ is populated with aliases to the subroutine
arguments. That is, a change to $_[0] affects $element, which in turn
affects @array. My *guess* is that somewhere in complex_function(), you
are using a variable called @array, which is in the process of being
modified in your foreach loop that called it.

I could be completely wrong, of course. Show us your code to get better
assistance.
Program 1.0:
$element = 5;
$answer = complex_function($element);

Calls complex_function with the value of 5.
Program 2.0:
$array[0] = 5;
for $element (@array) {
$answer = complex_function($element);
}

Calls complex_function with the value of 5.
Program 2.1:
$array[0] = 5;
for $element (@array) {
$element = 5;
$answer = complex_function($element);
}

Calls complex_function with the value of 5.
Program 2.2:
$array[0] = 5;
for $element (@array) {
$element = scalar $element;
$answer = complex_function($element);
}

Calls complex_function with the value of 5.
Program 2.3:
$array[0] = 5;
for $element (@array) {
$answer = complex_function(scalar $element);
}

Calls complex_function with the value of 5.



As noted, all 5 of these programs are doing exactly the same thing. It is
what happens inside complex_function that needs to be examined.


Paul Lalli
 
T

Tad McClellan

Stuart Kendrick said:
i'm realizing that i don't understand the distinction between list and
scalar context as thoroughly as i would like.


Maybe you need to get some really good Perl training from somewhere...

in the examples below,
Program 1.0 delivers a different $answer than do Programs 2.x [and
Programs 2.x all deliver the same $answer.]


It would be much easier for us to help fix the problem if we could
duplicate the problem.

We would need the definition of complex_function() in order to
duplicate the problem. (hint)

Feel free to email it to me if you like, and I'll have a look at it.



It appears to me that complex_function() should be returning the
same thing for every call that you have shown.

Can't tell what is going on without having code that I can run...

in Program 2.x, i understand that i'm using the "for" operator as an
alias for the "foreach" operator ... and that "foreach" surrounds
$element with a list context (whereas, in Program 1.0, $element is
surrounded in a scalar context.)


No, the loop control variable ($element in this case) of a foreach
is _always_ in scalar context.

The LIST that the foreach is to walk across (in the parenthesis)
is in list context though.

i can vaguely see that perhaps
complex_function knows the context surrounding $element


complex_function() is in scalar context in all of your code examples.

... and that
it produces different results, depending on $element's context.


$element does not _have_ a context, it is _in_ a context.

The errrr, context, around $element will determine the context
that $element is in.

It is in scalar context in _all_ of the foreach's, as I've mentioned above.

It is in list context in all of the function argument lists, because
function args are passed as a list in Perl.

i don't understand why complex_function cares ... i wrote
complex_function, after all.


All functions care what context they are called in.

(but _all_ of the function calls you've shown are in a single (scalar)
context anyway.)

perldoc perlsub

a C<return> statement may be used to exit the
subroutine, optionally specifying the returned value, which will be
evaluated in the appropriate context (list, scalar, or void) depending
on the context of the subroutine call.

functions can "see" what context they were called in, even if the
programmer didn't account for that when writing the function.

but hey, i'm willing to buy the idea
that it does. so now, i want to force Program 2.0 to place $element
in a scalar context, rather than in a list context.


If you mean $element when it is in a function's argument list,
then it cannot be done.

Function arguments are passed as a list in Perl.

because it turns
out that when complex_function runs in Programs 2.x, i don't get the
output i want;


It would be much easier for us to help fix the problem if we could
duplicate the problem. (hint again)

Program 1.0:
$element = 5;
$answer = complex_function($element);


Program 2.0:
$array[0] = 5;
for $element (@array) {
$answer = complex_function($element);
}

Program 2.1:
$array[0] = 5;
for $element (@array) {
$element = 5;
$answer = complex_function($element);
}

Program 2.2:
$array[0] = 5;
for $element (@array) {
$element = scalar $element;
$answer = complex_function($element);
}

Program 2.3:
$array[0] = 5;
for $element (@array) {
$answer = complex_function(scalar $element);
}
 
S

Stuart Kendrick

hi paul, hi tad,

yes, i can see myself headed back to Perl class sometime soon ... i
want to add oop to my tool kit ... i'm a procedural programmer right
now ... but that's another story.

thank you for the insights around context -- i'm understanding this
aspect of Perl better now!


but anyway, yes, posting actual running code makes a big difference in
your ability to give me the help for which i'm asking ... i realize
that ... but i wasn't hopeful that anyone could run my actual code,
even if i posted it.

however, now i'm hoping that if you can *see* my actual code, that you
can offer me insights into what i'm missing. i've spent the last
couple hours hacking my real code apart, to produce the version below.
this runs in my environment. one needs the Net-SNMP tool kit (5.x)
installed, complete with the bundled Perl module (*not* the old
version on CPAN, the one bundled with the current rev of the Net-SNMP
toolkit) ... plus a Cisco ethernet switch with SNMP enabled on it.

&snmpBulkWalkc is "complex_function" in the pseudo-code i posted
previously.


Here is output from the working version:

guru> ./prog1

Entering main::compile_mibs
Leaving main::compile_mibs
$VAR1 = [
bless( [], 'SNMP::VarList' )
];
guru>

And here is output from the broken version (see comments on how to
convert the working version into the broken version):

guru> ./prog2

Entering main::compile_mibs
Leaving main::compile_mibs
$VAR1 = [
'1',
bless( [], 'SNMP::VarList' )
];
Can't use string ("1") as an ARRAY ref while "strict refs" in use at
../prog2 lin
e 74.
guru>

obviously, i have a working version ... i mean, i can solve this
problem ... but since i don't understand why the 'broken' version
behaves differently than the 'working' version ... i'm concerned that
i'm missing something important here, something which will actually
bite me one day.

insights appreciated.

--sk


[both working and broken versions are also available at
http://vishnu.fhcrc.org/snmpbulkwalk]


#!/usr/bin/perl

# This script illustrates how to acquire MAC addresses from a switch
# using Cisco's 'community string indexing' approach

use strict;
use warnings;
use Data::Dumper;
use SNMP;

# Declare variables
our $debug; # Debug level
our %snmpRead; # SNMP read-only string, keyed by host
our $snmpPort; # UDP port on which remote SNMP daemon
# is listening
our $snmpRetries; # Number of requests to send before
# giving up
our $snmpTimeout; # Microseconds to wait before declaring
# a request lost
our %snmpVersion; # SNMP version, keyed by host


# Declare package Main variables
my $csIndex; # 'community string index'
my $esx; # Device to query
my $iid; # iid from varbind
my $max; # Maximum number of VLANs in an
# 802.1d esx
my $oid; # OID holding the table of interest
my $ref_AoAoVarbinds; # Reference to an array containing an
# array of Varbinds
my $sprint; # Value of the 'UseSprintValue'
# parameter
my $val; # val from varbind
my @vlans; # List of VLANs living within $esx
my $varbind; # Blessed reference to a 4 element
# array: [<obj>, <iid>, <val>, <type>]
my @AoAoVarbinds; # An array containing an array of
# varbinds

# Define package Main variables
$esx = "10.1.3.4";
$max = 10000;
$oid = "dot1dTpFdbAddress";
$sprint = 1;
$vlans[0] = 1;

# Define global variables
$debug = 2; # 4 = Enable detailed SNMP debugging
# 3 = Enable grody debugging
# 2 = Enable verbose debugging
# 1 = Enable basic debugging
# 0 = Disable debugging
$snmpRead{$esx} = "public";
$snmpPort = 161;
$snmpRetries = 1;
$snmpTimeout = 6000000;
$snmpVersion{$esx} = 2;


##### Begin Main Program ###################################


# This complies MIB files
compile_mibs();

# Walk list of vlans
for (my $i = 0; $i < @vlans; $i++) {
my $vlan = $vlans[$i];

#### To convert this program to the one which doesn't work, comment
#### out the two lines above, and uncomment the line below
# for my $vlan (@vlans) {

# SNMP Walk OID
$csIndex = $snmpRead{$esx} . "\@" . $vlan;
$ref_AoAoVarbinds =
snmpBulkWalkc($csIndex, $esx, '0', $max, $oid, $sprint);

# Dump the data
print Dumper($ref_AoAoVarbinds);

# Walk through the data structure
for (my $i = 0; defined $ref_AoAoVarbinds->[0][$i]; $i++) {

# Pull out a single varbind
$varbind = $ref_AoAoVarbinds->[0][$i];

# Pull out first item
$iid = $varbind->iid;

# Pull out second item
$val = $varbind->val;

print "iid = $iid\n";
print "val = $val\n\n";

}

}



#######################################################################
# Compile MIB files
#######################################################################
sub compile_mibs {
my $mibDirs = shift;
my $mibFiles = shift;

# Debug trace
if ($debug) { trace_location("begin") }

# Initialize
SNMP::initMib();

# Add mibDirs
for my $mibDir (@$mibDirs) {
SNMP::addMibDirs($mibDir);
}

# Add mibFiles
for my $mibFile (@$mibFiles) {
SNMP::loadModules($mibFile);
}

# Debug trace
if ($debug) { trace_location("end") }

return 1;
}



########################################################################
# Given a host, an OID, and a 'community string index' (and optionally
# the value of UseSprintValue), walk a tree, returning a reference to
# an array containing the results. Use SNMP GETBULK requests for
# efficiency. This routine supports Cisco's 'community string
# indexing' approach to extracting VLAN specific information from
# switches. Search Cisco's site for 'Community String Indexing'
########################################################################
sub snmpBulkWalkc {
my @answer; # List of values from tree
my $csIndex = shift; # 'community string index'
my $host = shift; # Host name to query
my $nonRepeaters = shift; # Number of $obj which should not
# be iterated over
my $maxRepeaters = shift; # Maximum number of iterations over
# $obj
my $obj = shift; # Place from which we start to walk
my $sprint = shift; # Value of UseSprintValue parameter
my @obj; # List of objects from tree
my ($sess, $val, $vb);

# Debug trace
if ($debug > 2) { trace_location("begin") }

# Sanity check
if ($snmpVersion{$host} == 1) {
print("Cannot call snmpBulkWalk against an SNMP v1 agent:
$host\n");
return;
}

# Define UseSprintValue
unless (defined $sprint) { $sprint = 0 }

# Debug info
if ($debug > 3) { print "snmpbulkwalkc on $host for $obj\n" }

# Define SNMP session
$sess = new SNMP::Session ( Community => $csIndex,
DestHost => $host,
RemotePort => $snmpPort,
Retries => $snmpRetries,
UseSprintValue => $sprint,
Timeout => $snmpTimeout,
Version => $snmpVersion{$host}
);

# Walk the tree
$vb = new SNMP::Varbind([$obj]);
$val = $sess->bulkwalk($nonRepeaters, $maxRepeaters, $vb);

# Debug info
if ($debug > 3) {
if (defined $sess->{ErrorStr}) {
print "ErrorStr = $sess->{ErrorStr}\n";
}
}

# Debug trace
if ($debug > 2) { trace_location("end") }

return $val;
}




########################################################################
# Show the programmer where we are
########################################################################
sub trace_location {
my $location = shift;
my ($subroutine) = (caller (1))[3];

if ($location eq "begin") {
print "\nEntering $subroutine\n";
}
elsif ($location eq "end") {
print "Leaving $subroutine\n";
}

return 1;
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,743
Messages
2,569,478
Members
44,898
Latest member
BlairH7607

Latest Threads

Top