Re: MAIN(*magic)

2024-03-15 Thread Bruce Gray


> On Mar 14, 2024, at 21:57, Mark Devine  wrote:
> 
> Rakoons,
>  
> I keep running into a space with Raku where it would be nice to have some 
> magic to reduce boilerplate.  I often make roles & classes with interface 
> options that flow from a consuming script’s MAIN arguments, then the first 
> bit of code at the top the MAIN block, then into instantiation.  If a person 
> makes nice utility libraries with lots of options for the user & employs them 
> a lot, the time consumed typing out the boilerplate adds up & bloats the 
> code.  Is there any way to shorten this pattern?  I was thinking about a 
> ‘switchable’ trait for attributes (with the naivety of a schoolboy).
>  
> my class Output {
> has $.csv  is switchable;
> has $.html is switchable;
>   .
>   .
>   .
> has $.xml is switchable;
> }
>  
> my class Timer {
> has $.countis switchable;
> has $.expire   is switchable;
> has $.interval is switchable;
> }
>  
> sub MAIN (
> Output.switchables, #= switchables from Class 'Output'
> Timer.switchables,  #= switchables from Class 'Timer'
> ) {
> my Timer $t.= new: :$expire, :$interval, :$count;   # variables 
> magically appear in scope
> my Output $o   .= new: :$csv, :$html, … , :$xml;# variables 
> magically appear in scope
> }
>  
> I estimate 10-50 lines of boilerplate could be removed from most of my Raku 
> scripts with something like that.  Unfortunately, I don’t possess any dark 
> magic for such things or I’d put forward an attempt.
>  
> Thanks,
>  
> Mark

Like lizmat, I also see no way to achieve your goal using the "is switchable" 
approach you have posited.
However, the thought of removing so much boilerplate pricks my thumbs, Ostara 
approaches, and it just turned midnight here, so Dark Magic will be attempted.

Thoughts:
* You don't need to have a $expire variable, or materialize any variable to 
allow feeding a CLI argument to a class' `.new()` constructor. You can skim off 
the arguments from @*ARGS, the same way many of the Getopt:: modules do. If you 
need them later, you can get them from the built object, e.g. `$t.count`.
* I expect is is harmless to expose *all* of the BUILDable attributes of the 
interfaced object types (which we can get via introspection!), so no need to 
mark individual attributes; you could mark the class itself with `is 
switchable`. 
* We could automate that `is switchable` (now for the whole class instead of 
its attributes) with a role, but why? More flexible to wrap (via module) *any* 
class/module with an OO API, even if not your own code! Proof-of-concept below.

Outline:
use Getopt::Attributes; # Not a real module (yet!)
my $sc = auto-cli( SomeClass );
sub MAIN ( ...just normal params, no need to list the params for all 
the objects built with `auto-cli`...  ) {
... code here can use `$sc`, which was created using args from 
the command-line ...
}


Test runs of code below:
$ raku poc_01.raku --count=5 --xml=foo.xml myfile.txt 
Timer  object  Timer.new(count => 5, expire => Any, interval => Any)
Output object  Output.new(csv => Any, html => Any, xml => "foo.xml", logger 
=> "localhost")
Remaining ARGS [myfile.txt]
Filename   myfile.txt

$ raku poc_01.raku --count=5 --xml=foo.xml --logger=abc myfile.txt 
Timer  object  Timer.new(count => 5, expire => Any, interval => Any)
Output object  Output.new(csv => Any, html => Any, xml => "foo.xml", logger 
=> "abc")
Remaining ARGS [myfile.txt]
Filename   myfile.txt

$ raku poc_01.raku myfile.txt 
Timer  object  Timer.new(count => Any, expire => Any, interval => Any)
Output object  Output.new(csv => Any, html => Any, xml => Any, logger => 
"localhost")
Remaining ARGS [myfile.txt]
Filename   myfile.txt



Actual proof-of-concept in a single file: poc_01.raku

# XXX In a fully-realized solution, this part would be in its own module.
# If it was Getopt::Attributes , it would live in 
lib/Getopt/Attributes.rakumod
# unit class Getopt::Attributes:ver<0.0.1>;

# Given a class, `auto-cli` introspects that class for its public accessors, 
knowing that those are valid named parameters for the class constructor. It 
then does a scan on @*ARGS, removing arguments that could be intended for that 
class. This must happen *before* MAIN runs, or MAIN will throw a USAGE error on 
the unexpected (from MAIN's perspective) arguments. The removed command-line 
arguments are merged with any provided defaults, and an instance of that class 
with the merged named arguments.
sub auto-cli ( Any:U $class, *%defaults ) is export {
my %opt = %defaults;

my @attrs = $class.^attributes
  .grep( *.has_accessor )
  .map(  *.name.subst(/^\S\S/) );
my $attr_re = /@attrs/;

for @*ARGS.keys.reverse -> $i {
if @*ARGS[$i] ~~ / ^ '--' ($attr_re) '=' (\S+) $ / {
my ($key, 

Re: MAIN(*magic)

2024-03-15 Thread Elizabeth Mattijsen
> On 15 Mar 2024, at 03:57, Mark Devine  wrote:
> 
> Rakoons,
>  I keep running into a space with Raku where it would be nice to have some 
> magic to reduce boilerplate.  I often make roles & classes with interface 
> options that flow from a consuming script’s MAIN arguments, then the first 
> bit of code at the top the MAIN block, then into instantiation.  If a person 
> makes nice utility libraries with lots of options for the user & employs them 
> a lot, the time consumed typing out the boilerplate adds up & bloats the 
> code.  Is there any way to shorten this pattern?  I was thinking about a 
> ‘switchable’ trait for attributes (with the naivety of a schoolboy).
>  my class Output {
> has $.csv  is switchable;
> has $.html is switchable;
>   .
>   .
>   .
> has $.xml is switchable;
> }
>  my class Timer {
> has $.countis switchable;
> has $.expire   is switchable;
> has $.interval is switchable;
> }
>  sub MAIN (
> Output.switchables, #= switchables from Class 'Output'
> Timer.switchables,  #= switchables from Class 'Timer'
> ) {
> my Timer $t.= new: :$expire, :$interval, :$count;   # variables 
> magically appear in scope
> my Output $o   .= new: :$csv, :$html, … , :$xml;# variables 
> magically appear in scope
> }
>  I estimate 10-50 lines of boilerplate could be removed from most of my Raku 
> scripts with something like that.  Unfortunately, I don’t possess any dark 
> magic for such things or I’d put forward an attempt.

An interesting idea!

Unfortunately I don't see this happening in the legacy grammar.  And it may 
still be pretty hard to do in RakuAST.

If you'd want to have a solution now, I'd look at *generating* the boilerplate 
code where appropriate.  This approach is used in the core in several 
locations, most notably recently in the localization modules.


Liz



MAIN(*magic)

2024-03-14 Thread Mark Devine
Rakoons,



I keep running into a space with Raku where it would be nice to have some magic 
to reduce boilerplate.  I often make roles & classes with interface options 
that flow from a consuming script’s MAIN arguments, then the first bit of code 
at the top the MAIN block, then into instantiation.  If a person makes nice 
utility libraries with lots of options for the user & employs them a lot, the 
time consumed typing out the boilerplate adds up & bloats the code.  Is there 
any way to shorten this pattern?  I was thinking about a ‘switchable’ trait for 
attributes (with the naivety of a schoolboy).



my class Output {

has $.csv  is switchable;

has $.html is switchable;

  .

  .

  .

has $.xml is switchable;

}



my class Timer {

has $.countis switchable;

has $.expire   is switchable;

has $.interval is switchable;

}



sub MAIN (

Output.switchables, #= switchables from Class 'Output'

Timer.switchables,  #= switchables from Class 'Timer'

) {

my Timer $t.= new: :$expire, :$interval, :$count;   # variables 
magically appear in scope

my Output $o   .= new: :$csv, :$html, … , :$xml;# variables 
magically appear in scope

}



I estimate 10-50 lines of boilerplate could be removed from most of my Raku 
scripts with something like that.  Unfortunately, I don’t possess any dark 
magic for such things or I’d put forward an attempt.



Thanks,



Mark