in reply to Re: Ordering of parameters - Re^9: Converting Unicode
in thread Converting Unicode

Apparently, I'm the only one who ever needs scores of variables passed into a single subroutine

No you are not. BUT pause a moment and think if those variables are just general configuration, settings which can span hundreds of variables. For example, often my subs require a verbosity value and a logger object (or filehandle) to dump debugging information. I can code these two variables into the signature of any sub. And call the sub like: distance($from, $to, $verbosity, $logger). The last two are optional though, if they are undef then it just logs nothing.

Inevitably, at some future time I need to refactor and add another parameter, let's say $timeout, and converting the sub to distance($from, $to, $verbosity, $logger, $timeout). It is a headache to refactor all my code to accommodate this new variable.

In the above, verbosity, logger are optional. The way I handle the complexity is to call them system-wide configuration. And put them into a hash. This hash is created at runtime by reading a configuration file:

$config = { verbosity => 3, logger => new Logger(), ... # hundreds of configuration settings };

And it is now passed around the subs like distance($from, $to, $config). If I need to add a new config variable like timeout, there is no big refactoring. Or, the refactoring is the same but now it is optional and can be done just for those subs that need the new setting, as the needs change.

But then I need to sometimes override a config setting for just one sub, e.g. the timeout. So, why not pass a hash(ref)? Something like this:

$params = { from => ..., to => ..., # override the timeout of the system-wide config timeout => ..., # this is the config read from file config => { verbosity => 3, logger => ..., timeout => 5, ... } }; sub distance { my $params = shift; # the timeout value will be the config's unless # caller passed their own value to temporarily override it my $timeout = exists($params->{timeout}) ? $params->{timeout} : $params->{config}->{timeout} ; ... # similarly, I return back multiple values in a hash(ref) return { errormsg => 'nothing', status => 1, # all OK! distance => 42 }; } # call it my $ret = distance($params); die $ret->{errormsg} unless $ret->{status} == 1;

Now, suppose that a sub needs to save some results for access by other subs to be run later. Well, why not save that into the config under $config->{stash}->{results-of-distance} = { ... }

But what I am showing here is just reinventing the OOP wheel:

# I am skipping some boilerplate package Calc; sub new { my ($configfile, ...) = @_; my $config = read_configfile($configfile); my $internal_data = { config => $config, stash => {} }; # bless etc. } sub distance { my ($internal_data, $params) = @_; # internal_data holds config, # $params holds from, to and anything to override # config just for the life of this call ... # optionally save results to the stash $internal_data->{stash}->{distance} = 42; # return something back return { errormsg => 'nothing', status => 1, # correct distance => 42 }; }

And you use this as:

my $calcobj = Calc->new('settings.config'); my $res = $calcobj->distance({ from => ..., to => ..., # optionally temporarily # override system-wide config setting timeout => ..., });

In the above example you do not have anything global which should be anathema to any programmer.

All data is inside the object, encapsulated so-to-speak. You can have two of those objects running alongside each with a different configuration file. Pass these objects around and you pass: 1) their config data, 2) their state (stash), 3) their logic (subs, algorithms).

I hope that was gentle. Note that I used hashrefs.

bw, bliako

Replies are listed 'Best First'.
Re^3: Ordering of parameters - Re^9: Converting Unicode
by Polyglot (Chaplain) on Dec 22, 2023 at 08:47 UTC
    +1 from me for that, bliako. Thank you. I'm sure others reading here will be helped by it. As for me, I'm lost at the very first line of your example code where it appears you are assigning a hash to a scalar. If I ever tried coding something like that, the program would likely put up a fatal error, and I would have no clue what I'd done wrong so as to fix it. I see code like yours out there regularly. It's just too much over my head, so I bring my head back under those clouds and do the best I can with what I can understand. I've read the OOP sections of "Programming Perl" multiple times, I've poured over others' code aplenty, yet my mind has never grasped it. My usage of OOP is limited to copy/pasting code that is sufficiently self-contained as to need little adaptation. It's a bit like copying the answers on a math test--if the teacher suspected me of cheating and asked me to do some novel problem, showing my work, I'd fail for sure.

    Essentially, I've got to know the ways to program without OOP due to my limitations--which means no use of classes, methods, blessings, arrayrefs, hashrefs, etc. unless I can just copy/paste a working solution from somewhere. I don't even understand the why of abstractions--like why in the world is it supposed to be better? Much less so can I grasp the how or the syntax of it.

    From what you're saying about using hashrefs, honestly, it sounds almost like a substitute for globals--just in a more "politically correct" form. But that probably shows I understand nothing about hashrefs.

    Blessings,

    ~Polyglot~

      I'm lost at the very first line of your example code where it appears you are assigning a hash to a scalar.

      I feel your pain!
      For a long time, this sort of syntax was alien to me. But it is worth taking time to understand.

      $config = { verbosity => 3, logger => 12, };

      This is assigning an anonymous hashref.
      Consider this instead:

      # Create a hash my %myhash = ( 'verbosity', 3, 'logger', 12 ); # Assign it to a hashref my $myhashref = \%myhash; # To get the same result # without the hash variable my $myhashref = { verbosity => 3, logger => 12, };

      For many years, references were beyond me.

      It is easy to just say that we don't understand them and dismiss that as the way that it will always be. This is what Carol Dweck describes as a "Closed Mindset". The alternative is to reframe the problem. Instead, say that we don't know how to do it yet but we can learn - it might be difficult, but it is possible. Then the only question is whether one is prepared to put in the effort and be humble enough to ask for help and receive wise counsel from those who provide it without getting defensive when information and methods that don't make sense are presented. Nothing makes sense until it has been learnt...that's why people used to think the Earth was flat...

      I asked here in the Monastery about references. Several Monks gave conceptual models that didn't make sense and examples that I didn't understand. I tried to use references in test code and failed. I tried changes, sometimes at random, until I got something working or, at least, giving a different error message. I asked if my understanding of what was happening was right. Slowly I grew to understand...

      You too can understand references, OOP and trigonometry...but it requires an open mindset...

      I'm lost at the very first line of your example code where it appears you are assigning a hash to a scalar.

      Please read perlreftut.


      🦛

      All is global in the computer's RAM!!!!! ;)

      A global variable is bad style not because it is just there but because you don't know who uses it. So, trivial example, if you change its name you rely on Perl's strictness to tell you who from those using it still use the old name which is now undefined and you need to fix it.

      $config in the line that confused you is only global in the computer RAM and in its limited scope when it was created from a file. ***I CHOOSE WHO SEES/ACCESSES THAT $config*** by passing it's reference (which is an address to the computer RAM which does not need to concern you at all) explicitly to them (subs). That's hugely different than declaring it global and any sub deciding to access it. Many times I revisit my code and can't remember a thing. Worst things happen when others work on your code. So, difficult to keep track where all the globals are used across my code. So, it is not political correctness. @^$^@* political correctness AFAIC. It is a way to deal with complexity. And a good one too.

      Anyway, I feel that I may confuse you, so I will not insist on this subject unless you ask.