how to force scalar context

Discussion in 'Perl Misc' started by Stuart Kendrick, Jul 12, 2004.

  1. 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);
    }
     
    Stuart Kendrick, Jul 12, 2004
    #1
    1. Advertising

  2. Stuart Kendrick

    Paul Lalli Guest

    On Mon, 12 Jul 2004, Stuart Kendrick wrote:

    > 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
     
    Paul Lalli, Jul 12, 2004
    #2
    1. Advertising

  3. Stuart Kendrick <> wrote:

    > 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...

    <grin>


    > 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);
    > }



    --
    Tad McClellan SGML consulting
    Perl programming
    Fort Worth, Texas
     
    Tad McClellan, Jul 13, 2004
    #3
  4. 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;
    }
     
    Stuart Kendrick, Jul 13, 2004
    #4
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Jack Dowson
    Replies:
    1
    Views:
    313
    Chris Dollin
    May 1, 2007
  2. Clint Olsen
    Replies:
    6
    Views:
    376
    Jeff 'japhy' Pinyan
    Nov 13, 2003
  3. dutone
    Replies:
    8
    Views:
    110
    dutone
    Jul 2, 2004
  4. Ian Wilson

    list vs scalar context for localtime?

    Ian Wilson, Oct 25, 2004, in forum: Perl Misc
    Replies:
    5
    Views:
    128
    Uri Guttman
    Oct 26, 2004
  5. Mark

    Replace scalar in another scalar

    Mark, Jan 27, 2005, in forum: Perl Misc
    Replies:
    4
    Views:
    171
    Arndt Jonasson
    Jan 27, 2005
Loading...

Share This Page