0

I am new to Perl and I got a doubt with the usage of the variable in the subroutines.

    #! /usr/bin/perl

    $x=4;
    &routine;
    print "value of x in main is $x\n";

    sub routine
    {
      local $x = 10;
      print "value of x in routine is $x\n";
      print "value of x in main is $x\n";       #what should be replaced in $x to get correct answer
    }

As in the program, what should be replaced in this line

    print "value of x in main is $x\n";

to get the value of $x variable in main function?

2 Answers2

1

The "local" statement effectively hides the original value of the global variable. If you need the original value, you will have to make a copy before the "local" declaration:

$x=4;
&routine;
print "value of x in main is $x\n";

sub routine
{
    my $originalX = $x;
    local $x = 10;
    print "value of x in routine is $x\n";
    print "value of x in main is $originalX\n";
}

The most useful propery of "local" as opposed to "my" is that the local value still visible in functions that are called from within the scope of the local variable.

our $x=4;
foo();
print "value of x in main is $x\n";

sub foo {
  local $x = 10;
  print "value of x in foo is $x\n";
  bar();
}

sub bar {
  print "value of x in bar is $x\n";
}

results in

value of x in foo is 10
value of x in bar is 10
value of x in main is 4
1

There are a few things to understand here in regards to variables and how perl treats and uses them.

When you declare $x and assign the value 4, you are actually defining a package variable. When not using strict pragmas (enabled by the use of use strict 'vars' or use strict), you do not have to preceed the variable declaration with either my or our. As such, Perl will then default to initializing $x as a package variable. Packages are set via the package keyword and if that is omitted then perl defaults to the main package. This means you have created a package variable called $main::x with value 4. Whilst remaining in the package main, you can use the alias $x to mean $main::x.

A package variable can be utilized anywhere within your main package scope (often referred to as global variables), and this is why you can access $x in the subroutine routine().

local will store away the value of $x for the duration of the scope it was declared at until the end of the scope it was declared in. So in your example, the scope of the local declaration is for the whole of the routine() (between where local was used and the closing } brace of the routine() declaration). When leaving the scope, it reinitializes $x to the stored value. This is why the print statement after calling routine() shows $x as 4.

First to answer your immediate problem:

Because local is specific to the closure it was utilized in, you can create a separate closure with in routine(). This way you can localize $x within that scope, but retain the package variable of $x within your subroutine:

#! /usr/bin/perl 

$x=4;  # declare a package variable $main::x
routine();
print "value of x in main is $x\n";

sub routine {
    # now create a closure, so we can localize the package variable
    # within its own scope
    {
        local $x = 10;
        print "value of x routine, within locally scoped closure is $x\n";
    }
    print "value of x _from_ main is $x\n";  #will now print 4
}

As touched on in other answers, best practice in perl is to use strict pragmas as well as asking for warnings when errant coding is detected:

use strict;
use warnings 'all';

As such running your code will then give:

Global symbol "$x" requires explicit package name.

We can solve this in a couple of ways, we can either declare it with a package name:

$main::x = 4;

and then have to implictly refer to it in the rest of the code as $main::x.

Or if we prefer to have access to aliases, we can use the keyword our to declare $main::x as a package variable, and then refer to it as $x in the rest of our code.

our $x=4;      # (is the same as writing $main::x = 4, but you also
               # can refer to it by its alias $x from this point onwards).

With these points covered you can then have the final recommended solution:

#! /usr/bin/perl 

use strict;
use warnings 'all';

our $x=4;  # declare a package variable $main::x
routine();
print "value of x in main is $x\n";

sub routine {
    # now create a closure, so we can localize the package variable
    # within its own scope
    {
        local $x = 10;
        print "value of x routine, within locally scoped closure is $x\n";
    }
    print "value of x _from_ main is $x\n";  # will now print 4
}

Extra Information

Note that localized variables remain in scope, even when other subroutines are called within that scope:

our $x=4;
routine();

sub routine {
    {
        local $x = 10;
        print "routine() localized, x is $x\n";
        another_routine();
    }
    print "routine() x is $x\n";
}

sub another_routine {
    print "another_routine() still localized, x is $x\n";
}

Will output:

routine() localized, x is 10
another_routine() still localized, x is 10
routine() x is 4

We have not touched open lexical variables declared by the my keyword (sometimes referred to as private variables or my variables). These are different in behaviour that they only live for the duration they remain in scope (well technically until their reference count becomes 0, but that's another topic!). This allows us to declare variables which (for example) only get created and used during your routine() subroutine:

sub routine {
    my $y = 20;

    print "y is set to $y during the duration of routine()\n";
}

my also has the subtle effect of allowing us to re-use package variable names, and use private values for that variable, for the duration of the scope they were declared in. Note, they do not behave like localized variables, and calling other routines within scope will default to using the package variable value:

our $x=4;
routine();

sub routine {
    my $x = 20;

    print "routine() x is $x\n";
    another_routine();
}

sub another_routine {
    print "another_routine() x is $x\n";
}

will output:

routine() x is 20
another_routine() x is 4

$x inside routine() is private to routine() and only routine().

I hope the lanuague is clear enough to understand!

Drav Sloan
  • 1,532
  • 8
  • 13