Beefy Boxes and Bandwidth Generously Provided by pair Networks
XP is just a number
 
PerlMonks  

[DSL] disabling packages in Perl?

by LanX (Saint)
on Apr 18, 2016 at 17:10 UTC ( [id://1160828]=perlquestion: print w/replies, xml ) Need Help??

LanX has asked for the wisdom of the Perl Monks concerning the following question:

Hi

is there a way to (temporarily) disable packages?

let's say I wanted to create a DSL for SQL, this partial clause would be legal in Perl

sub WHERE(&;$); WHERE { a IN [1,2,3] or b LIKE '%abc%' };

and the block would be parsed as indirect method syntax

'IN'->a([1, 2, 3]) or 'LIKE'->b('%abc%');

so I'd need to define packages IN and LIKE to simulate binary operators when executing the BLOCK

DB<198> {package LIKE; sub AUTOLOAD { my ($field) = $AUTOLOAD =~ m/\ +w+::(\w+)/; print "$field @_" }} DB<199> field LIKE '%abc%' field LIKE %abc%

so far so good ... but is there a possibility to "disable" the package outside of the block's scope?

The best thing I came up was redefining AUTOLOAD dynamically before and after executing the block to provoke a runtime error

deleting the stash entry for the package in %main:: produced strange problems, probably because of some kind of cashing...

any ideas?

EDIT

at hindsight %INC and @INC might be useful ... (?)

Cheers Rolf
(addicted to the Perl Programming Language and ☆☆☆☆ :)
Je suis Charlie!

Replies are listed 'Best First'.
Re: [DSL] disabling packages in Perl? (updated)
by haukex (Archbishop) on Apr 18, 2016 at 17:58 UTC

    Hi Rolf,

    The three things that come to my mind are unimport via no, namespace::clean (update: or namespace::autoclean or namespace::sweep), or lexically scoped pragmas via %^H/perlpragma. I haven't used any of them in a module of my own, so I can't say much more, but maybe one of them is a starting point?

    Update: That was just a bit of brainstorming - I guess the first two ideas are effective at package scope, the third at lexical scope, and then of course there's dynamic scope... I guess I'm not sure what scope you're thinking about "disabling" the package at? Also, by "disabling", do you mean actually undefining the methods, or just setting a flag such that they fail when called outside of the appropriate scope (whichever one that is)? And one more thought: Do you really need to disable the packages, or is it just a "safety" measure against accidental usage outside the correct scope? I imagine that packages named IN and LIKE are unlikely to clash with other packages...

    Regards,
    -- Hauke D

      Thanks a lot ... unfortunately I'm too busy like now to test it all out or elaborate much further.

      > I imagine that packages named IN and LIKE are unlikely to clash with other packages...

      That's true but I'm looking for a general "solution" ...

      > Also, by "disabling", do you mean actually undefining the methods, or just setting a flag such that they fail when called outside of the appropriate scope (whichever one that is)?

      hmm good question...

      Ideally I'd like to be able to attach a "domain" to a code block to automatically have package and uses prepended, something like

      sub SQL(&) :prepend('package mySQL; use DSL::mySQL;') { ... }

      and whenever I execute

      SQL { SELECT {} FROM {} WHERE {} }

      then the code block is already parsed with methods imported from DSL::SQL which are not visible outside of the code block. OTOH variables from the outer scope would be visible inside the inner scope.

      some background¹ ...

      I'm meditating about internal DSLs ("Domain Specific Languages") and ideally a "sub language" could be included adding new "syntax" (or better phrased syntactic sugar) not leaking to the outer scope.

      Traditionally we often use outer DSLs defined in strings/files which need to be parsed and evaluated. Take for instance a template system like TT.

      CGI.pm used to have an inner DSL to build HTML, there is also Jade for HTML in JS or LINQ for SQL in .NET .

      Cheers Rolf
      (addicted to the Perl Programming Language and ☆☆☆☆ :)
      Je suis Charlie!

      ¹) well this should better be in the meditation section ...

        Hi Rolf,

        I've finally had some time to think about this some more. I don't think there's a clear answer here, but I've included some sample code in which I've experimented with a few things.

        I realized that automatically switching packages inside an SQL { ... } block might not be such a good idea, because it breaks something as simple as sub get_value { ... }; SQL { xyz LIKE get_value() };. So instead, it might make sense to use a module or a pragma that is only effective in its lexical scope, i.e. SQL { use SQL_DSL; ...; no SQL_DSL; }.

        I agree that %^H does not seem to be useful to have something "unimported" or "undefined" at the end of a scope, it could only disable the functions like in my version "one" above. However, there is B::Hooks::EndOfScope, which is used by all three namespace::clean, namespace::autoclean and namespace::sweep! Or, one of the two methods above (pluggable keywords or source filters) could theoretically be used to insert a no statement at the end of the block. However, the problem that does remain is that these two methods are not particularly robust.

        I haven't yet come up with any better ways to enable and disable syntactical features in a certain lexical scope at compile time without reaching into the internals (then again I'm not that much of an expert, so I could be missing something - I did run across Devel::Declare but haven't had a chance to look at it more closely*). It might be possible to define your DSL in a way that a SQL { ... } block complies cleanly, and then the features used within it are only enabled at run time (sub SQL (&) { enable_features(); shift->(); disable_features(); }), but that might be too limited. On the other hand, if you're trying to add syntactic sugar that goes too far beyond what Perl can do natively, you're essentially defining a mini-language that extends Perl. And if you don't want to slap a source filter around your whole program, the simplest solution might be: sub SQL ($) { ... };  SQL q{ WHERE b LIKE %abc% };

        A lot of the above thoughts are more about the technical "how" than the "why" or "what for". Of course a lot depends on what you want your DSL to look like and what you want it to do. For SQL, there's already SQL::Abstract and SQL::Statement, or perhaps, as you mentioned elsewhere in this thread, you want something more like CGI's HTML generation statements, select(from(...),where(like('b','%abc%'))...

        Anyways, that's just some more thoughts on the matter, maybe it helps!

        Regards,
        -- Hauke D

        * Update: From the documentation of MooseX::Declare:

        Warning: MooseX::Declare is based on Devel::Declare, a giant bag of crack originally implemented by mst with the goal of upsetting the perl core developers so much by its very existence that they implemented proper keyword handling in the core.

        As of perl5 version 14, this goal has been achieved, and modules such as Devel::CallParser, Function::Parameters, and Keyword::Simple provide mechanisms to mangle perl syntax that don't require hallucinogenic drugs to interpret the error messages they produce.

      Hi again

      > or lexically scoped pragmas via %^H/perlpragma.

      I've put some hope into this, because pragmas like strict are automatically disabled at the end of the scope.

      In reality thats because $^H is localized per scope and has bits flagging the compile time behaviour.

      %^H in contrast is only hint where an imported func() can look-up at run-time if it is still in the same scope where it was imported. :/ (see example in perlpragma )

      There doesn't seem to be any possibility to automatically trigger unimport

      I.e. in the following example the imported func() will still exist outside the functions scope as long I don't trigger an explicit unimport.

      sub test { BEGIN {warn "---- function-scope\n"}; use Module; func("function-scope"); # no Module; # uncommenting will cause a compile-time error } test(); func('filescope');
      out

      ---- function-scope * importing from Module running func(function-scope) at d:/exp/t_lexical_unimport.pl line 13. running func(filescope) at d:/exp/t_lexical_unimport.pl line 19.

      Module.pm:

      So the only thing I can do is to hack func() in a way to check %^H at run-time if it is still called in the originating scope.

      This might be possible for subs but is a hassle for exported variables. (I could only try to use a tie to intercept any access and check $^H )

      Am I missing something???

      Cheers Rolf
      (addicted to the Perl Programming Language and ☆☆☆☆ :)
      Je suis Charlie!

Re: [DSL] disabling packages in Perl?
by shmem (Chancellor) on Apr 18, 2016 at 19:19 UTC

    A pragma ruling scoped visibility of packages (and methods/functions) at compile time would be a wonderful thing to have. I'm not aware of this being possible within our current perl. Maybe if I did read the documentation more thoroughly...

    Unless we find answers for that in the current perl, this should be addressed to p5p as a feature request.

    Thanks for raising this question.

    perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
Re: [DSL] disabling packages in Perl?
by mlawren (Sexton) on Apr 19, 2016 at 12:56 UTC

    I can't comment on your question regarding the disabling of package however I have spent some time trying to build an SQL DSL in Perl.

    One issue you will likely face is dealing with things like "or" and "and" short-circuiting: as soon as Perl sees a true value on the left side of "or" or a false value on the left side of "and" it does not evaluate/execute the other term. Another issue is the flattening of "(" and ")" lists losing information you need to pass on to the database.

    The best thing that I could come up with was parsing a list as special key/value pairs like so:

    my $pinfo = $self->db->xhashref( select => [ 'n.id', 'p.hash' ], from => 'nodes n', inner_join => 'projects p', on => 'p.id = n.id', where => { 'n.uuid' => $uuid }, ); # Try and do this with an ORM! $dbw->xdo( insert_into => [ 'func_new_topic_status', qw/change_id id project_id tkind status rank def bill/, ], select => [ qv( $opts->{change_id} ), 'nextval("nodes")', qv( $opts->{id} ), 'ts.tkind', 'ts.status', 'ts.rank', 'ts.def', qv( $opts->{bill} // 1 ), ], from => 'topic_status ts', where => { 'ts.project_id' => $dup_pinfo->{id} }, order_by => [qw/ts.tkind ts.rank/] );

    The DBIx::ThinSQL module contains my latest efforts. Unfortunately the documentation status is woefully wrong, but it functions (in my very humble opinion) far more powerfully and efficiently that any ORM or other SQL DSLs I've seen on CPAN.

      > like "or" and "and" short-circuiting:

      Thanks, I'm fully aware of this! :)

      My initial plan was to patch B::Deparse in a way to take the op-tree of the code-block and to produce SQL instead of Perl (or rather a semantically correct intermediate Perl representation which produces SQL)

      > flattening of "(" and ")"

      likewise, true lists are flattened but precedence is still visible in the op-tree

      BUT for the moment I'm rather inclined to create subs OR() and AND() for a quick prototype.

      That is taking multiple expressions after a prefixed operator.

      WHERE { OR ( number EQ 3, AND ( customer LIKE '%...%', name IN qw/John Jane/, ) ) }

      Thats not really much overhead, because when mixing AND and OR parens should always be applied to highlight precedence.

      I'm very busy ATM but I'll show a proof of concept after next weekend.

      Cheers Rolf
      (addicted to the Perl Programming Language and ☆☆☆☆ :)
      Je suis Charlie!

        > Thats not really much overhead, because when mixing AND and OR parens should always be applied to highlight precedence.

        I would agree about the parens but there is a certain cognitive overhead in disassembling (or creating) the reverse polish notation which I have always shied away from. In the end for complicated expressions I think (I would have to check as it's been a while) I went for a mix of arrayrefs and hashrefs (implicit AND):

        select => [qw/expr1 expr2/], from => 'table', where => [ { number => 3 }, OR, { 'customer LIKE' => '%...%', name => [qw/John Jane/] }, },

        I'm looking forward to seeing what you come up with. Please post it!

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1160828]
Front-paged by Arunbear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others admiring the Monastery: (7)
As of 2024-04-19 06:58 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found