Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?

Passing a variable recursively

by timtowtdi (Sexton)
on Nov 04, 2012 at 20:49 UTC ( #1002242=perlquestion: print w/replies, xml ) Need Help??
timtowtdi has asked for the wisdom of the Perl Monks concerning the following question:

First, I know there has been written a lot about this issue, but I still have some issues with it. (Basically: I just don't understand :) )

I think I can explain my question the best with the factual example. What I want is setting a log-level in the main program, and let the printing be done by a module.

I've written my first Object-Oriented module, named:

#!/usr/bin/perl package PrintToScreen; use strict; my $version = "0.1"; ############################### # Verbosity # # 0 = Error # 1 = Normal # 2 = Verbose # 3 = Debug # ############################### sub new { my $class = shift; my $self = { _usersetloglevel => shift, }; my $usersetloglevel = $self->{_usersetloglevel}; if ($usersetloglevel >= 3) { print "$class $version started with loglevel $usersetloglevel\ +n"; } bless $self, $class; return $self; } sub Print { my ( $self, $msglevel, $message ) = @_; if ($msglevel <= $self->{_usersetloglevel}) { print "$message"; } } 1;

And some perl-program to call that module and print, depending of course, on the log-level.
#!/usr/bin/perl use strict; use warnings; use PrintToScreen; use ConnectMySQL; # just declaration my $loglevel = 3 ; my $pts_obj = new PrintToScreen( $loglevel ); $pts_obj->Print( 1, "Message with level 1! :-)\n"); $pts_obj->Print( 2, "Message with level 2! :-)\n"); $pts_obj->Print( 3, "Message with level 3! :-)\n");

So far it works, but here's my issue:

I also use a module, named, and I would like to pass the value of the log-level to that ConnectMySQL-module. That ConnectMySQL-module also contains 'use PrintToScreen'. So the log-level should be the same as the main program that's calling it.

Of course, the main idea behind al this that I can set the verbosity (/loglevel) while programming on 3, and when everything is finished, set it to 1, so it behaves normal. (including the used modules recursively)

I am afraid that my explanation stinks, but I still think you'll get my point.

For example the looks like this: (just imagine the mysql-connect-code)

#!/usr/bin/perl package ConnectMySQL use strict; use PrintToScreen; my $pts_obj; $pts_obj->Print( 3, "About to connect to mysql-database... "); #connect blablabla $pts_obj->Print( 3, "Done!\n");

Replies are listed 'Best First'.
Re: Passing a variable recursively
by tobyink (Abbot) on Nov 04, 2012 at 21:28 UTC

    The closest thing to what you describe would be to make "PrintToScreen" a singleton object. Your new method would be something like:

    my $instance; sub new { my ($class, $loglevel) = @_; $instance ||= do { print "$class $VERSION started with loglevel $loglevel\n" if $loglevel >= 3; bless { _usersetloglevel => $loglevel }, $class; }; }

    That way, when your code calls PrintToScreen->new it will always return the same object, at the same loglevel.

    Now, it needs to be said that singletons are generally considered code smell - i.e. a sign of poorly thought out code. There are valid use cases for them, but not many. Using a singletons for logging will come back to bite you when you decide you'd like for part of your code (perhaps the network interaction but, say) to be logged in great detail, but not the rest of your code. (Perhaps because you're trying to figure out a bug in the networking stuff without being distracted by hundreds of irrelevant messages from parts of the system you know are working well.)

    Better for your logger to be a normal class so you can do:

    my $network_connection = NetworkConnection->new; $network_connection->set_logger( PrintToScreen->new(3) ); my $database_connection = DatabaseConnection->new; $database_connection->set_logger( PrintToScreen->new(1) );

    See also: Stack Exchange: Why is Global State so Evil?

    PS: not sure why you're using "recursively" in your message title. Recursion in computer science is when a function (directly or not) calls itself. A quick example of recursion to implement integer multiplication using just addition and subtraction:

    use strict; use warnings; sub multiply_integers { die "two positive integers!\n" if @_ != 2 || grep { not /^[1-9][0-9]*$/ } @_; my ($x, $y) = @_; return $x if $y == 1; return $x + multiply_integers($x, $y-1); } print multiply_integers(7, 6), "\n";
    perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'
      Thanks for the fast reply. For the 'recursive-thing': Yes, you're right. What I meant was what I think you call cascade: So if a module uses a module and that module uses a module again...

      But then I think you're right that it just comes down to a Global State.

      And trust me, In this case, I would also already be happy with that! :-)

      I'm gonna check if I'll get you're suggested code to work.

      Thanks again!

      The great mistake is to anticipate the outcome of the engagement; Let nature take it's course, and your tools will strike at the right moment.
Re: Passing a variable recursively
by rjt (Deacon) on Nov 04, 2012 at 23:19 UTC

    If I understand you correctly, tobyink's reply fits pretty well. One more thing to note, however, is that there already exists a plethora of Perl logging modules, many of which will probably do what you want.

    If you're mainly doing this as a learning exercise, however, then, again, tobyink left you some valuable advice.

      Thank you too!

      I will definitely take a look at plethora logging modules! My main goal now is to understand tobyinks post, but just for this situation, these modules might could help me out too.
      The great mistake is to anticipate the outcome of the engagement; Let nature take it's course, and your tools will strike at the right moment.
Re: Passing a variable recursively
by marinersk (Priest) on Nov 06, 2012 at 07:36 UTC
    OO noob here, so I'm just asking without having tested this yet, but --

    Wouldn't another approach be to pass the logger object to the module if you wish it to use that logger object instance?

    something like:

    # calling program stuff my $loggerHandle = new myLogger(); my $workerHandle = new myWorker(); my $returnValue = $workerHandle($loggerHandle, $Parameter, $Option, $E +tc);

    You achieve the intended result, which is a lot like Global State, but not with a Global State implementation. You tell the subordinate method which logger to use, and its logging behavior is driven by its previously-established logging level attribute.

    Or am I out in left field here, and worse, the game is soccer?

      Precisely. You should give functions and objects everything they'll need to perform their duties. Loggers, database handles, etc should be provided to objects as part of their constructor, or passed to each method/function call.

      The name for this general style of programming is "dependency injection" - you inject into an object/function everything it depends on. Generally speaking it is considered a good thing to do.

      That said, it is possible to go far with dependency injection. If a function needs to fetch something from the web, it's a good idea to allow people to "inject" an LWP::UserAgent or compatible object. But if a function needs to multiply two numbers together, then allowing people to inject their own implementation of integer multiplication as a coderef is a step too far. Generally common sense will tell you what dependencies should be injectable and what can be hard-coded.

      perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1002242]
Approved by marto
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others imbibing at the Monastery: (2)
As of 2018-04-26 02:40 GMT
Find Nodes?
    Voting Booth?