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

Imagination greater than reality?

by writch (Acolyte)
on Jul 10, 2017 at 21:54 UTC ( #1194749=perlquestion: print w/replies, xml ) Need Help??
writch has asked for the wisdom of the Perl Monks concerning the following question:

I thought that there's probably some kind of dynamic way to use a library, but I haven't found it yet. Most of my attempts end up being a syntax error, or don't error but don't work.

My idea is that I can have libraries for different states so that I can calculate a value differently in Texas than in Georgia.

Obviously, this doesn't work, but it's what I've got in my head:

my $state = 'TX';
use State::$state::formulas;
Is what I'm thinking of outside of the current box, or am I just doing the right thing the wrong way?

Replies are listed 'Best First'.
Re: Imagination greater than reality?
by 1nickt (Prior) on Jul 10, 2017 at 22:10 UTC

    Using the core module Module::Load:

    use Module::Load; my $state = 'TX'; my $module = 'State::' . $state . '::formulas'; eval { load $module; } or do { my $err = $@; ... };
    There are more ways to do it, this is one :-) (update: huck showed an even lower-level one while I typed, ++)


    The way forward always starts with a minimal test.
Re: Imagination greater than reality?
by huck (Priest) on Jul 10, 2017 at 22:05 UTC
      I want to thank everyone for the thoughtful and creative input. I ended up using this method. It's funny, but as I was typing in the original post last night I was wondering if joining the string in that fashion would work.
Re: Imagination greater than reality?
by Marshall (Abbot) on Jul 10, 2017 at 22:39 UTC
    The solutions from huck and 1nickt look fine to me. Note: BrowserUk's idea about require is also applicable to this problem.

    I don't know whether you need the flexibility of different algorithms between states or just different parameter values, like "state sales tax" or whatever.

    A different approach to consider might be the SQLite DB. This DB uses a simple file instead of a separate SQL server process. It works very well (I think every smartphone has a SQlite DB). The Perl DBI for SQLite is excellent and I've used it on a number of projects; some small and some with millions of records. Your module could access the DB to get values for whatever state you tell it you are in.

    Also one point to consider is maintenance. In general maintaining a single US DB is probably easier than 50 different state specific modules expressed as Perl code. I know very little of your actual application. If this sounds like it is worth pursuing for you, happy to provide more information and suggestions.

    Update:
    Another try at an explanation: If you can express the functions for each state as a single set of "code" with 50 different sets of "data", I would recommend the DB approach even if this complicates the code logic in this "single set" of code. If you have 50 different sets of code, that can vastly complicate the maintenance.

    What you apparently want to do may adopt itself well to an OO paradigm. The user program says: "use StateFormulas;". When creating a new StateFormula object, specify the state. When the object is created, data is read from the DB. This object for say TX (Texas) now works differently than one for say IA (Iowa) because of differing parameters. If say the Texas calculations are somehow different because it has a coastline, then I would put a field in the DB like "has_coastline". The code will perhaps wind up some "if" statement, but that if statement will apply to all states with a coastline. If you go this way then code could wind up with easy comparisons between different states because each object in the program would be specific to a state rather than saying "hey now this program works specifically for TX". Hope this helps.

      I don't know whether you need the flexibility of different algorithms between states or just different parameter values, like "state sales tax" or whatever.

      FYI, some states have local as well as state-wide taxes. Also, different categories are taxed differently in different states, sometimes even different local taxes. I would not be surprised if it were significantly easier to write code for each state than to try to devise a set of tax tables.

        I would not be surprised if it were significantly easier to write code for each state than to try to devise a set of tax tables.
        Actually I would expect that maintaining 50 different sets of code would be significantly more effort than a single set of code with a DB, even if the code is significantly more complex to write in the first place. 50 different algorithms is a difficult thing to get one's brain around. If say some flaw is found in the Texas algorithm, then it could be a big problem figuring out which of the other 49 sets of code are affected.

        Another factor can be just the on-going updates of the per state information. I doubt the OP is working with "sales tax" - that was just the first obvious "per state" idea that popped into my brain. There are companies who specialize in DB's and software to deal with the complex plethora of US tax laws. A long time ago I had a friend who was a salesperson for a company like that. What multi-state and multi-national companies do with this tax stuff is super complicated and worth "big bucks". For a number of reasons, "roll your own tax code" is not a good idea. My mention of this was just a "for instance, example", not anything deeper than that.

        Back to the generic programming issues... Whatever it is that changes between the states, a solid program "product" will have a way to keep things "right" and "updated" past the first program release. As a dev engineer, I want to write new code that solves new problems. I can't do that if "my released product code" is not maintainable or extensible by somebody else.

        I have no problem with the solutions to the OP's problem which address it directly - they look fine to me. My goal was to present another possible approach to the OP's problem.

        One issue is "hey, its been 2 years, how do I know that this per state stuff is "up to date"? How does somebody get "notified" that something needs to change for Texas? As another issue, an OO paradigm seems like a good idea here. I would not put knowledge of the states into the stateObject. I would use some parameter like 'TX','AX','NTX'(North Texas) when creating the "StateObject" and give a clear error message comprehensible to the expected "user". The new StateObject looks into the DB and initializes its behavior based upon that. If Puerto Rico, PR ever becomes a state (which it probably won't), add another line to the DB. The testing code can make 50 different state objects and run the same data against those objects and compare results. A situation where the program starts and then is "customized" to a specific state is not as flexible of an approach.

        From what I have read, the OP is proceeding with a solution that works for him. Fine. There is often "more than one right answer" here. My comment are for others who may follow with similar issues.

Re: Imagination greater than reality?
by BrowserUk (Pope) on Jul 10, 2017 at 23:14 UTC

    Try require.


    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority". The enemy of (IT) success is complexity.
    In the absence of evidence, opinion is indistinguishable from prejudice. Suck that fhit
Re: Imagination greater than reality?
by kcott (Chancellor) on Jul 11, 2017 at 06:42 UTC

    G'day writch,

    "Is what I'm thinking of outside of the current box, ...?"

    Not outside the current box, or even outside an old box: the builtin module File::Spec, for instance, has been doing this sort of thing for years. [I'm fairly sure it's more than a decade, but I don't have specific information to hand.]

    "... or am I just doing the right thing the wrong way?"

    If you're getting errors, or it's not working as expected, then probably "the wrong way". :-)

    I would consider reordering the elements of your namespace such that the least specific element is first and the most specific last. It's your module, you can call it whatever you like, and there may be aspects of which I'm not aware; however, I probably would have chosen:

    Formula::State::$state

    You can keep generic code in Formula::State, perhaps something like:

    sub calculate_xyz { my ($x, $y, $x, $state_variance) = @_; $state_variance //= 1; return (($x + $y) / $z) * $state_variance; }

    Then in Formula::State::$state:

    ... calculate_xyz($x, $y, $x) ... # Use standard value ... calculate_xyz($x, $y, $x, 1.5) ... # Add 50% to standard value ... calculate_xyz($x, $y, $x, 2) ... # Double standard value

    That also keeps your state-related formulae separate from Formula::Molecular, Formula::Secret, and so on.

    You're showing use, which suggests that you want to load your module at compile time. You can do that with something like this:

    BEGIN { my $state = ... get state from somewhere (e.g. $ARGV[0]) ... require "Formula/State/$state.pm"; "Formula::State::$state"->import(@optional_arguments); }

    I ran a quick command line test (mainly to check the syntax I'd given you):

    $ alias perle alias perle='perl -Mstrict -Mwarnings -Mautodie=:all -E' $ perle 'BEGIN { my $x = $ARGV[0]; require "List/$x.pm"; "List::$x"->i +mport("uniq") } my @u = uniq (1,1,2,3,3,3); say "@u"' Util 1 2 3 $ perle 'BEGIN { my $x = $ARGV[0]; require "List/$x.pm"; "List::$x"->i +mport("uniq") } my @u = uniq (1,1,2,3,3,3); say "@u"' MoreUtils 1 2 3

    I initially used the first() function, which I thought was in both of those modules. It's not, so I got an error; however, that's also useful feedback.

    $ perle 'BEGIN { my $x = $ARGV[0]; require "List/$x.pm"; "List::$x"->i +mport("first") } say first { not $_ % 2 } 1, 2, 3' Util 2 $ perle 'BEGIN { my $x = $ARGV[0]; require "List/$x.pm"; "List::$x"->i +mport("first") } say first { not $_ % 2 } 1, 2, 3' MoreUtils Could not find sub 'first' exported by List::MoreUtils at -e line 1. BEGIN failed--compilation aborted at -e line 1.

    — Ken

Re: Imagination greater than reality?
by sundialsvc4 (Abbot) on Jul 10, 2017 at 23:29 UTC

    What I most commonly do in this case is to create a so-called “factory method” – call it what you will, implement it as you like – which, given a state-code such as "TX", will return an instance of an object which corresponds to that state-code.   Furthermore, if given an invalid state-code, it will throw a meaningful exception.

    In this way, you centralize the process of instantiating the correct object, while also making that process opaque.   You have now given yourself flexibility:   if, say, “every state is identical except Texas,” then no one needs to know except the factory.   (Obviously, the factory, while instantiating the object, should somehow inform the new object which state-code it is to serve, and the object should provide its user with the means to inquire.)   You also provide a centralized and consistent way to detect any logic elsewhere in the program which is harboring an invalid state-code.   Elsewhere, you can now say with confidence that, “if I’ve got a state-thingy in my hands right now, it must have passed all of the tests for legitimacy that were imposed by the factory that successfully created it.”

    If the differences among the States are substantial, then I will indeed specifically follow BrowserUK’s recommendation to use require, and I will bracket this in exception-handling logic that will meaningfully report bugs like “Hey!   Even though I know that the module ought to be there, there’s no module for Texas!”   The factory is a clearing-house for centralizing all such concerns so that the rest of the program does not have to deal with them, and so that problems can be quickly diagnosed.

      What I most commonly do in this case ...

      Please provide a SSCCE. Thanks.

        I really don’t think that, in this case, I need to “let me write that for you.”   It should be quite self-evident that I am talking about some function which, given (at least) a state-code as its input, returns an instance of an object.   Any possible attempt by me to jimmy-up a code example would be utterly meaningless to whomever is actually going to act upon it, and also would be quite unnecessary.   (I prefer to assume that people who ask questions here, most especially when they speak as the OP did, actually know how to write Perl code and don’t need any further help from me.)

        Likewise, I don’t think that I need to “prove” anything to my Seven Anonymous Downvoters.®   Just amuse yourselves by casting seven downvotes on this response, too, and Be Happy.™   (I can’t help but notice that you seven poor souls seem to view this now-obligatory exercise as a boundless wellspring of amusement, even after all these years.  Tell me, do all you really not have anything else to do with your time?   Just askin.’)

        Meanwhile, I do believe that the OP had a question.

        Architecturally speaking, what you very much want to do is to centralize the process of “producing an appropriate object instance,” so that the necessary logic occurs in one place in the application ... and so that it is surrounded by suspicious error-checks.   Even if “the appropriate solution” consists of the OP’s method or BrowserUK’s subsequent suggestion of require, you emphatically do not want that logic to appear “all over the source-code of the program.”   Instead, you want to implement your solution in exactly one place, and call it from everywhere else.

        The code that instantiates any object, in any programming language, is very closely tied to the correct operation of the object itself.   Especially in a language such as Perl(5), which contains no compiler-provided notion of an object at all, this consideration should be treated very formally and carefully.   For instance, every object should be entitled to assume that its creation-time parameters have been checked.   (In Perl, it is much safer IMHO to do this before instantiating the object, rather than to do it in the constructor.)   In a language such as Perl, consolidating these duties into one sub is a very prudent move.

        This “one implementation, in one place,” is also a very natural place to check for errors bugs, and once again I strongly recommend that you do so aggressively.   Throw an exception if something ... anything ... is wrong.   Thus, any piece of code anywhere in the program that finds itself holding an instance of an object which must have been created by this factory, therefore also knows that every one of the “suspicious tests” performed by that factory must have succeeded.   (If some value within the object is now found to be bogus, then the corruption must have occurred after the object was instantiated.)   These are enormous time-savers when debugging a production program.

      What I most commonly do in this case is to create a so-called “factory method” ...

      Prove it

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1194749]
Approved by hippo
Front-paged by kcott
help
Chatterbox?
[Eily]: s/complete ness/complete mess/ :P
[Eily]: and you can overload "0+" rather than bool, as numification is used instead of stringification in boolean context when available

How do I use this? | Other CB clients
Other Users?
Others romping around the Monastery: (13)
As of 2017-07-27 14:00 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    I came, I saw, I ...
























    Results (415 votes). Check out past polls.