martedì 21 marzo 2017

SpeakerFight 6 PGDay.IT: è possibile?

Sono venuto a conoscenza per caso di un progetto interessante: SpeakerFight.
L'idea è abbastanza semplice, e l'implementazione mantiene la semplicità: si inviano dei contributi di talk (per conferenze ed eventi) e si lascia che le persone li votino, in un meccanismo stile "star" ben noto da altre piattaforme. I talk/contributi che hanno ricevuto il maggior numero di voti vengono selezionati per l'evento.

Un paio di giorni fa ho proposto di valutare questo meccasnimo nell'ambito del PGDay.IT. Da tempo sono sostenitore di una call-for-papers piu' aperta e con selezione maggiormente trasparente rispetto a quanto è avvenuto nelle ultime edizioni. Anzi, a dire il vero ho anche proposto piu' volte di fare un "speaker fight" del poveraccio addirittura per il keynote, proponendo di chiedere alla community chi fosse interessato a fare un keynote speech invece che andare su singolo invito diretto.

Ora sistemi come quello qui descritto hanno, ovviamente, i loro svantaggi: per esempio si potrebbe votare molto un talk tenuto da un perfetto incompetente che risulterebbe in uno speech di pessima qualità, trascurando magari talk meno "accattivanti" ma di sicuro successo ed impatto.
E forse alcune persone non vogliono selezionare di propria volontà i talk, quanto lasciare che siano gli organizzatori a "stupirli" con contenuti all'altezza di stimolare la curiosità e l'intelletto.
Tuttavia è difficile rimanere in un ambito o nell'altro se non si hanno dati alla mano circa il gradimento delle precedenti edizioni (questione che spesso ho sollevato).

Personalmente ritengo che aprire almeno una porzione del PGDay.IT ad un sistema di votazione diretta possa dare quella spinta ad autori e partecipanti per sentirsi maggiormente coinvolti e, soprattutto, per poter decidere il livello dei contenuti da visionare, garantendo quindi una maggiore partecipazione (almeno in teoria).
Se poi il tutto viene accompagnato anche da un feedback sui talk, si può avere una base dati abbastanza oggettiva che possa permettere un evento migliore ad ogni edizione.

Per sessioni interattive penso che un sistema di gradimento anticipato sia fondamentale: organizzare una sessione interattiva (es. ITPUG-Lab) non è semplice e rischia di portare via risorse (sia organizzatori che partecipanti) dalle track della conferenza. Occorre quindi essere certi del gradimento e della partecipazione alla sessione.

Insomma, una sfida interessante e uno spunto per i prossimi organizzatori dell'evento perché si possa sempre migliorare e non "sedimentare" su formule sempre uguali e ripetute.

domenica 19 marzo 2017

DBIx (DBIC) in 10 minuti

Per chi proviene dal mondo "classico" della programmazione ORM (Object Relational Mapping), il componente DBIX può risultare un po' ostico. Il problema principale, a mio avviso, è nella terminologia e nella modalità di utilizzo. Se un ORM classico infatti richiede solitamente un livello intermedio di persistenza (tipicamente tramite una sessione, dao, ecc), DBIX tende a fornire oggetti (proxy-zzati) molto piu' autonomi.
In questo brevissimo articolo voglio mostrare come può essere usato DBIX agilmente nelle operazioni piu' comuni.

1 Creazione del database

Si supponga di creare uno schema SQLite3 molto semplice:
sqlite3 database.db
sqlite> CREATE TABLE events( pk integer primary key autoincrement,
ts timestamp default current_timestamp,
description varchar(200) );

2 Eseguire il dump dello schema

Il programma dbicdump permette il dump di uno schema database, ovvero la creazione degli oggetti result che rappresentano ciascuno una tabella singola del database/schema.
 dbicdump -o dump_directory=./lib -o components='["InflateColumn::DateTime"]' \
  Test::Schema
  dbi:SQLite:database.db
Dumping manual schema for Test::Schema to directory ./lib ...
Schema dump completed.
Dopo aver eseguito questo comando si avrà una directory lib che contiene un file Schema.pm (che dichiara il namespace) e il relativo albero di directory Schema/Result con un file pm per ogni DBIX result, ovvero un file per tabella. In particolare, per la singola tabella event rappresentata dalla classe nel file Event.pm si ha che:
__PACKAGE__->add_columns(
  "pk",
  { data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
  "ts",
  {
    data_type     => "timestamp",
    default_value => \"current_timestamp",
    is_nullable   => 1,
  },
  "description",
  { data_type => "varchar", is_nullable => 1, size => 200 },
);
Si nota come il sistema di gestione dei result sia di tipo dichiarativo: ogni classe (Perl) indica in modo dichiarativo i propri membri, e ciascun membro corrisponde sostanzialmente ad una colonna del database.
L'utilizzo di DBIX risulta veramente veloce e semplice (dal punto di vista della quantità del codice). La classe schema eredita da DBIX::Schema e quindi dispone del metodo connect che consente la connessione al database. Ogni azione che si vuole fare su un database (istanza) deve avvenire attraverso lo schema. Lo schema raccoglie una serie di tuple raggruppate in un result set (inteso qui differentemente rispetto a JDBC, DBI, ecc). Ogni operazione che si vuole svolgere su una tupla deve passare attraverso un result set. In sostanza si potrebbe affermare che l'analogia fra DBI e DBIX è la seguente:
DBI - JDBC - similari DBIX Significato
database handler schema Connessione al database (istanza)
result set array di oggetti Risultati da una query
statement su tabella result set Interazione lettura/scrittura con una tabella

3 Un primo esempio

Ecco quindi un programma di esempio:
use Test::Schema;

my $schema_connection = Test::Schema->connect( 'dbi:SQLite:/home/luca/tmp/dbix/database.db' );


for ( 1..100 ){
    my $event = $schema_connection
 ->resultset( 'Event' )
 ->create( { description => "Evento $_" } );

    # update !
    $event->update();
}

# ricerca di eventi
my $result_set = $schema_connection->resultset( 'Event' )->search( { pk =>{ '>' => 50, '<' => 87 }  } );

while ( ( my $event = $result_set->next() ) ){
    say "Evento " . $event->pk . " = " . $event->description;
}
#+end-perl

La prima parte carica lo schema e gli oggetti creati appositamente da ~dbicdump~.
Successivamente si crea lo schema, ovvero ci si collega al database (istanza).
Poi si effettua un semplice ciclo per inserire 100 righe nella tabella /events/, corrispondente all'oggetto Perl ~Event~.
Si noti che tali oggetti sono creati con il metodo ~create~ sul result set, che a sua volta è agganciato alla tabella /events/.
In altre parole la riga

#+negin_src perl
my $event = $schema_connection
 ->resultset( 'Event' )
 ->create( { description => "Evento $_" } );
esegue:
  1. una connessione al database (istanza) usando lo Schema;
  2. decide di lavorare sul result set Event, ovvero sulla tabella corrispondente events;
  3. chiedendo di creare un oggetto che rappresenta una singola riga nella tabella.
Giunti a questo punto DBIX sa tutto quello che gli serve per memorizzare in modo permanente sul database l'oggetto, e quindi tradurlo in una riga di SQL. Da notare che l'oggetto creato tramite il metodo create è un oggetto Perl a tutti gli effetti, ovvero è possibile usare i metodi accessor per impostare i valori.
Fino a qui DBIX si è solo "preparato", ma non ha eseguito nulla di fatto. Nel momento in cui l'oggetto si aggiorna, ovvero si invoca il metodo update, questo viene inserito/aggiornato nella tabella.
Questo è un punto nodale che rappresenta una differenza fondamentale nell'approccio DBIX rispetto ad altri ORM (es. Hibernate): invece che creare degli oggetti slegati dal database e richiedere ad un servizio DAO di rendere persistente tali oggetti, DBIX crea oggetti collegati alla propria tabella e al proprio database, così che tali oggetti "sappiano" dove andare ad "inserirsi" o "aggiornarsi". Nessuna cosa, entità o servizio di persistenza esterno viene invocato.
Successivamente è possibile rileggere i dati inseriti, ancora una volta passando per il result set:
  1. si usa la connessione al database (istanza), ovvero lo Schema;
  2. si dichiara di voler lavorare sullo schema Event, ovvero la tabella fisica events;
  3. si effettua una ricerca specificando, per ogni colonna che si vuole cercare, i valori da mettere in and-logico. Nell'esempio di cui sopra si ha che si prendono tutte le tuple con pk compreso fra 51 e 86 inclusi. L'oggetto ritornato è un Result Set, da intendersi come collezione di oggetti del tipo Event.
Si può allora iterare sul result set per ottenere tutti gli oggetti di tipo Event richiesti. Da notare che è possibile specificare delle clausole where letterali, il metodo search può accettare diversi formati di parametri. Inoltre esiste un metodo find per la ricerca secca di un record per chiave primaria.

4 Update massivo

Ci sono due strade per effettuare un aggiornamento massivo di record:
  1. iterare su ogni oggetto collegato ad una tupla ed eseguire un update singolo;
  2. eseguire un update sul result set trovato da una query di ricerca.
Il primo metodo potrebbe riassumersi così:
my $result_set = $schema_connection->resultset( 'Event' )->search( { pk =>{ '>' => 50, '<' => 87 }  } );

while ( ( my $event = $result_set->next() ) ){
    $event->description( " AGGIORNATO " . $event->description() );
    $event->update();
}
L'idea è semplice: si effettua una ricerca, si prendono uno ad uno gli oggetti Event (ciascuno dei quali corrisponde ad una tupla) e lo si aggiorna tramite update. DBIX è sufficientemente intelligente per capire che deve eseguire una UPDATE verso il database, siccome la riga proveniva da una query.
Il secondo approccio, sicuramente piu' compatto e simile, come concetto, alla query SQL che si vorrebbe eseguire, è il seguente:
my $result_set = $schema_connection->resultset( 'Event' )->search( { pk =>{ '>' => 50, '<' => 87 }  } );
$result_set->update( { description => 'AGGIORNATO' } );
La differenza, ovvia, è che non si possono chiamare degli accessor nel caso di update di un intero result set, e quindi questa tecnica è effettivamente utile quando si devono impostare tutti i valori ad un letterale prestabilito.

5 Colonne modificate, annullare le modifiche

Un singolo oggetto collegato ad un result set è in grado di sapere quali colonne sono state modificate:
$event->description( " AGGIORNATO " . $event->description() );
    my %cols = $event->get_dirty_columns();
    for my $col ( keys %cols ){
 say "Evento " . $event->pk . " con colonna $col modificata " ;
    }
e quindi è anche possibile evitare di eseguire delle query qualora l'oggetto non sia stato modificato:
$event->update() if ( $event->get_dirty_columns() );

6 Cancellazione

Beh, come ci si puo' aspettare, delete esegue lo scopo:
$event->delete;

7 Insert o Update?

Inizialmente ho mentito: non è necessario chiamare update per eseguire una query INSERT o una query UPDATE. Come ci si può aspettare il metodo update esegue…eh.. UPDATE! Esiste infatti un metodo insert da richiamare su un oggetto risultato per eseguire l'inserimento nella tabella corrispondente. Il motivo per il quale nel primo esempio si è usato update è che lo schema ha creato l'oggetto con create, che inserisce di fatto una tupla nella tabella corrispondete. L'update successiva aggiorna tale tupla. E' possibile creare un oggetto senza inserirlo nel database usando il metodo new sul result set, che restituisce un oggetto sul quale occorre poi fare una insert esplicita:
my $event = $schema_connection
    ->resultset( 'Event' )
    ->new( { description => "!!Evento $_" } );

$event->insert;

8 Aggiungere foreign keys

Supponiamo di voler dotare ogni evento di un "tag":
sqlite> CREATE TABLE tags( pk integer primary key autoincrement, description varchar(20) );
sqlite> INSERT INTO tags( description) VALUES( 'Tag1' ), ( 'Tag2' );
sqlite> DROP TABLE events;
sqlite> CREATE TABLE events( pk integer primary key autoincrement,
   ...>    ts timestamp default current_timestamp,
   ...>    description varchar(200),
   ...>    tag_pk integer,
   ...>    FOREIGN KEY(tag_pk) REFERENCES tags(pk) );
Se si ricostruisce il modello Perl con dbicdump si ha che viene creata una classe Tag che contiene nella definizione il seguente blocco:
=head1 RELATIONS

=head2 events

Type: has_many

Related object: L

=cut

__PACKAGE__->has_many(
  "events",
  "Test::Schema::Result::Event",
  { "foreign.tag_pk" => "self.pk" },
  { cascade_copy => 0, cascade_delete => 0 },
);
che istruisce DBIX sul fatto che Event e Tag sono in relazione attraverso le chiavi esterne. Analogamente Event viene modificato rispetto a prima:
__PACKAGE__->belongs_to(
  "tag_pk",
  "Test::Schema::Result::Tag",
  { pk => "tag_pk" },
  {
    is_deferrable => 0,
    join_type     => "LEFT",
    on_delete     => "NO ACTION",
    on_update     => "NO ACTION",
  },
);
Vediamo ora come è possibile inserire delle tuple che agiscano su entrambe le tabelle:
use Test::Schema;

my $schema_connection = Test::Schema->connect( 'dbi:SQLite:/home/luca/tmp/dbix/database.db' );


for ( 1..100 ){
    my $tag = $schema_connection
 ->resultset( 'Tag' )
 ->find( ( $_ % 2 ) + 1 );

    my $event = $schema_connection
 ->resultset( 'Event' )
 ->new( { description => "Evento $_" } );

    $event->tag_pk( $tag );
    $event->insert;
}
Come si può notare si seleziona in modo "random" un tag dal resultset Tag, e poi lo si imposta nell'oggetto Event che viene poi inserito tramite insert. Da notare una bruttura: DBIX chiama il metodo accessor della relazione come la colonna stessa della tabella, in questo caso tag_pk. Ovviamente non è DBIX, quanto dbicdump, quindi questa cosa va tenuta presente e/o modificata a mano per rendere piu' leggibile il codice Perl (dopotutto tag_pk() agisce su un oggetto Tag e non su un oggetto integer). Se ad esempio si modifica Event.pm come segue:
__PACKAGE__->belongs_to(
  "tag",  # era tag_pk
  "Test::Schema::Result::Tag",
  { pk => "tag_pk" },
  {
    is_deferrable => 0,
    join_type     => "LEFT",
    on_delete     => "NO ACTION",
    on_update     => "NO ACTION",
  },
);
allora il seguente codice usa il metodo mnemonico tag che ricorda meglio l'oggetto risultato Tag:
for ( 1..100 ){
    my $tag = $schema_connection
 ->resultset( 'Tag' )
 ->find( ( $_ % 2 ) + 1 );

    my $event = $schema_connection
 ->resultset( 'Event' )
 ->new( { description => "Evento $_" } );


    $event->tag( $tag ); # era tag_pk
    $event->insert;
}
A questo punto si può procedere in maniera analoga alla ricerca dei dati, il metodo Event::tag fornirà un oggetto di tipo Tag mentre Tag::events fornirà una lista di oggetti Event. Ad esempio:
my $result_set = $schema_connection->resultset( 'Tag' )->find( 1 );

for my $event ( $result_set->events() ){
    say "Evento trovato " . $event->description;
}
da notare che sul result set non si deve effettuare nessuna operazione poiché il metodo find restituisce un solo valore Tag.

9 Riassunto

DBIX è un meccanismo molto flessibile e potente, che opera a livello semi-dichiarativo: come si è potuto notare ogni oggetto collegato ad una tupla database è espresso tramite invocazioni di metodi che ne definiscono struttura e relazioni (es. belongs_to). Il concetto chiave di DBIX è, a mio avviso, il fatto che il result set non rappresenta un set di risultati quanto una tabella fisica (o un join di tabelle), e che di conseguenza ogni singola tupla di tabella/e diventa un oggetto capace di agire autonomamente sulla propria persistenza. Ne consegue che ogni volta che si vuole interagire con una tabella si deve passare per un result set, sia per aggiungere, aggiornare, cancellare o selezionare tuple, che a loro volta diventano oggetti, che includono la logica dao al proprio interno.

sabato 18 marzo 2017

Stai attento ai tuoi soldi, non a quello che firmi!

C'è da un po' di tempo una pubblicità televisiva che mi ha lasciato molto amareggiato per il livello di stupidità planetaria raggiunta.
L'idea è quella di far notare, come che ce ne fosse bisogno, che i soldi non vanno sprecati e che stare attenti alle proprie spese è la prima mossa per
non rimetterci in denaro (e forse anche in salute).
Evidentemente chi ha ideato questa pubblicità non è mai stato mandato da piccolo a comprare il giornale o il pane; se ci fosse andato avrebbe ricevuto le
giuste raccomandazioni sul resto da riportare a casa.

Ad ogni modo la raccomandazione dello spot pubblicitario è giusto, anche se il fatto che uno spot commerciale sia mirato ad un risparmio mi fa sorridere.
E quindi, nell'evoluzione mentale che porta tutti a dover stare attenti ai propri soldi, possibile che esistano dei professionisti che
chiedono di firmare contratti errati, sapendo e ammettendo che lo sono?
Già, la fiducia galoppa, un po' come in altri spot dove si esortano i bambini a lasciare le porte di casa aperte…

Ed è proprio al grido di "fidiamoci" che si spingono le persone a firmare contratti densi di penali, mal scritti e peggio composti, e il cui scopo finale è sempre solo
il trattamento economico. Già, "fidatevi", che poi cambiamo a penna importi e condizioni, ma dopo che avete firmato.
Uhm…perché non prima?
Ah beh, non cambia, si potrebbe farlo anche prima, si potrebbe anche fare un contratto nuovo con le giuste cose al giusto posto.
E allora perché non lo si fa?
Ah già, fidiamoci…

E che piovano fuoco e fiamme sui poveri malcapitati dotati ancora di intelletto e che non sono disposti ad accettare un simile
atteggiamento "professionale".

Io sono disposto a fidarmi, ma solo di gente che mi mostra con i fatti che la loro parola vale, che la loro parola conta.
Di tutti questi "professionisti" non posso avere stima, compassione, e non lascio nemmeno che instaurino il dubbio nella mia mente.
Ma ritengo che ormai siamo destinati ad una follia di massa che ci spingerà tutti verso il basso.

Caino sta vincendo a mani basse.

mercoledì 15 marzo 2017

Lexpad, chiusure e variabili

Un articolo molto interessante e ben fruibile sulla definizione di variabili (e chiusure) e del loro livello "intermedio".
Effettivamente non mi ero mai posto il problema della differenza fra assegnamento e binding a livello di run-time.
Consiglio vivamente la lettura.

martedì 14 marzo 2017

Scacco matto!

Pur non essendo un abile giocatore di scacchi, ritengo di saperne a sufficienza per una partita amatoriale.
Sicuramente non mi sarei mai aspettato che mio figlio, alle prime armi e di soli 6 anni, riuscisse a farmi uno scacco matto.
Sia chiaro, si tratta di una casualità, non certo di genialità nascosta, ma comunque è stata per lui una grande soddisfazione!


domenica 12 marzo 2017

Scripting si o scripting no?

Scripting o programmazione?
Dibattito fondamentalmente inutile ma che ogni tanto salta nuovamente fuori nelle mailing list, forum e discussioni tecniche.
Fornisco la mia visione a riguardo, sottolineando che non intendo cerco convincere nessuno e, allo stesso tempo, non intendo essere convinto.

Inizialmente la distinzione era forse fin troppo facile: un linguaggio è di scripting se viene interpretato invece che compilato.
Ma il termine "interpretato" può confondere: qui si intende interpretazione non anticipata.
In altre parole immaginiamo di fare uno script di due righe, prendiamo in prestito una qualche shell Unix:

echo "Hello World!"
heco "Bye bye!"


Ora la prima riga è corretta, la seconda presenta un errore di sintassi.
Se il linguaggio è interpretato on-the-fly il risultato sarà quello di eseguire la prima riga, con stampa in output della frase, e il blocco per errore di sintassi quando si tenta di interpretare la seconda riga.
Se invece il linguaggio subisce una pre-analisi (sia questa una compilazione, controllo sintattico, ecc.) si avrà che l'esecuzione intera del programma viene abortita.

Su questa base è facile dividere i linguaggi in "scripting" e "programmazione".

Con l'andare del tempo però la differenza si è molto assotigliata. Si può affermare che Java è non-interpretato? Inizialmente forse lo era (si interpretava il bytecode), magari con compilatori JIT/HotSpot non vale piu' questa distinzione.
Eppure Java non ha mai concesso l'avvio di un programma con errori di sintassi (anche se alcuni tools, tipo Eclipse, consentono ora di inserire apposite eccezioni in blocchi di codice che non passano la compilazione, ma questa è un'altra storia).

Allora si potrebbe pensare che un linguaggio di programmazione deve avere una forma compilata "stoccabile", e in questo allora Java rientra visto che dispone dei file .class. Anche altri linguaggi, come ad esempio C/C++ forniscono i loro file object.

La pace nel mondo è tornata dunque!

Ma a guastare le cose vengono poi altri linguaggi, che solitamente non dispongono di un formato compilato, e che per ottimizzazioni spinte invece forniscono ora anche delle versioni compilate e stoccabili.

Allora la distinzione dipende dal fatto che vi sia a disposizione un compilatore da usare sempre?

Uhm...forse l'approccio dovrebbe essere differente.

La mia personale visione è che lo scripting sia qualcosa che viene fatto rapidamente, senza troppi prototipi, senza ore di design e refactoring.
E per fare questo non voglio un compilatore da invocare per sentirmi dire che non ho inizializzato una variabile!
Quindi se devo fare un programma Perl che mi esegua un find su una serie di cartelle, o un programma shell che mi effettui un backup regolare di un filesystem, allora dico che ho prodotto uno "script". Indipendentemente dal linguaggio, a patto che possa modificare il programma stesso con il solo uso di un editor di testo e nessun altro tool.

Ma se il programma diventa complesso, richiede una struttura dati solida, un albero sorgenti complesso e, perché no, un packaging, allora sto producendo un "programma".

In sostanza la differenza, nella mia testa, è data solo da dimensione e complessità. Il linguaggio di programmazione è solo uno dei tanti dettagli tecnologici, anche se la sua scelta può influenzare dalla partenza la complessità stessa e quindi il risultato finale.

Non farò MAI uno script in Java: il linguaggio richiede una classe, che abbia un main, magari un jar per eseguire o chissà che altra diavoleria.
Farò a volte dei programmi Perl: dovrò costruire moduli, classi, importare librerie e chissà che altro.

Sull'argomento è molto interessante l'articolo di Larry Wall.

venerdì 10 marzo 2017

Sacchi di monete... Perl!


C'è una baggianata che gira ultimamente su WhatsApp relativamente a mesi nei quali avvengono coincidenze stranissime: 5 domeniche, 5 sabati, ecc.
L'evento viene citato come rarissimo, tanto che si verificherebbe solo 823 anni (o qualcosa di simile).
Ovviamente si tratta del solito spam mirato solo a collezionare informazioni e numeri di telefono.


Ma sarà vero?
Basta scrivere qualche riga Perl per sapere quali sono i mesi nei quali sono presenti un certo numero di ripetizioni dello stesso giorno della settimana.
Ecco quindi la mia soluzione:



use v5.20;
use DateTime;
my $now = DateTime->now();

for ( 1..823 ){
    $now->set( day => 1, month => $_ )
    &&
    say "Anno " . $now->year . " mese " . $now->month
    . " ha 5 " . join ', ', qw( Lun Mar Mer Gio Ven Sab Dom )
    [ map {  $_ , ( $_ + 1 ) % 7 , ( $_ + 2 ) % 7  } ($now->day_of_week - 1 ) ]
        for ( grep { ( $_ % 2 == 0 && $_ > 7 ) || ( $_ % 2 == 1 && $_ <= 7 ) } ( 1..12 ) );

    $now->add( years => 1 );
}


L'idea è semplice: anzitutto si filtrano i mesi con 31 giorni, funzione svolta dall'operatore grep che estrae i numeri di mese 1,3,5,7,8,10,12.
Questi vengono usati per impostare il mese corrente, si seleziona il primo giorno e si mappa il day_of_week in una lista di tre valori: la posizione del primo
giorno della settimana e dei due successivi adiacenti (modulo 7, ovviamente per consentire il "giro"). Il tutto viene poi stampato.
Il trucco è che il ciclo è postfisso, quindi va letto da destra a sinistra; inoltre l'inizializzazione del mese corrente è in "and" logico alla stampa
del valore, fatto che consente di usare un solo ciclo.
Per andare avanti di un anno, beh, basta aggiungere un anno e reiterare.
L'output è semplice:


Anno 2838 mese 3 ha 5 Lun, Mar, Mer
Anno 2838 mese 5 ha 5 Sab, Dom, Lun
Anno 2838 mese 7 ha 5 Gio, Ven, Sab
Anno 2838 mese 8 ha 5 Dom, Lun, Mar

Questa soluzione si basa sul semplice fatto che tutti i mesi con 31 giorni hanno sempre 3 giorni consecutivi con ripetizione pari a 5.
Ma se non fosse noto ciò?
Beh, complicandosi la vita si può fare una soluzione general-purpose:


use v5.10;
use DateTime;

my ( $count , $count_per_month, $years, $now, $dow, $ldom, $cons )
    = ( 5, 3, 823,
        DateTime->now()->set( day => 1, month => 1 ),
        sub{ qw( Lun Mart Merc Giov Ven Sab Dom )[ $_[ 0 ] ] },
        sub{ $_[ 0 ]->month != $_[ 0 ]->clone->add( days => 1 )->month },
        sub{ $_[ 0 ] == $_[ 1 ] - 1 && $_[ 1 ] == $_[ 2 ] - 1 } ) ;

for ( 1..( 365 * $years ) ) {
    my $me = \%{ $days_of->{ $now->year }->{ $now->month } };
    ++ $me->{ $now->day_of_week - 1 };

    say "Anno " . $now->year . " mese " . $now->month . " => "
        . join ', ',
        map { $me->{ $_ } . " " . $dow->( $_ )  }
          sort keys %$me
        if ( $ldom->( $now )
             && ( grep { $_ >= $count } values %$me )
             >= $count_per_month
             && $cons->( ( grep { $me->{ $_ } >= $count }  sort keys %$me )  ) );


    $now->add( days => 1 );
}


Il funzionamento è abbastanza semplice, anche se un po' compatto.
Anzitutto mi affido a DateTime che fornisce una interfaccia ad oggetti per la gestione delle date.
Si inizializzano una serie di variabili:

  • $count rappresenta quante ripetizioni dello stesso giorno della settimana sto cercando (5);
  • $countpermonth rappresenta quante ripetizioni di $count ci devono essere in un mese. Ad esempio impostandolo al valore 3 significa
    cercare almeno 3 giorni in un mese che si ripetano 5 volte (ossia il mese deve avere, ad esempio, almeno 5 sabati, 5 domeniche e 5 lunedì);
  • $years su quanti anni da quello corrente si vuole iterare;
  • $now la data corrente, impostata all'inizio dell'anno corrente;
  • $dow è una routine di comodo che ritorna il nome simbolico del giorno della settimana (day of week);
  • $ldom altra funzione di utilità che controlla se l'oggetto DateTime corrente rappresenta l'ultimo giorno della settimana. In particolare si controlla
    se un DateTime successivo di un giorno ha un mese differente;
  • $cons è una funzione che controlla che gli indici siano consecutivi fra loro (es 1,2,3), anche se ci vorrebbe la logica modulo 7 per
    intercettare il giro di boa alla domenica…

Da qui in poi si itera in un ciclo di 365 iterazioni per anno, salvando in un hash a piu' livelli il conteggio dei giorni della settimana di un mese.
L'hash assume una struttura dove al primo livello si trova l'anno corrente, poi il mese, poi il giorno della settimana, poi il conteggio di tale giorno.
Ad esempio una ipotetica iterazione potrebbe produrre:


(
 2017 => 1 => ( Lun => 3,
                Mart => 3,
                Mer  => 4,
                Gio  => 5,
                Ven => 3
                Sab => 3,
                Dom => 3
);
La variabile $me serve solo a tenere il codice un po' piu' compatto, ossia ad accedere alla parte dell'anno/mese corrente
nel'hash globale.
Anzitutto si incrementa il contatore del giorno della settimana (già tradotto da numerico a mnemonico con $dow).
Segue un blocco say..if, quindi è opportuno partire prima dalla condizione if: se il giorno è di fine mese e si ha un conteggio di valori di singoli
giorni della settimana superiore a quello cercato si procede alla stampa. Questo consente di valutare se stampare il mese corrente solo se si è
iterato su tutti i giorni del mese e solo se si sono trovate almeni $count_per_month ripetizioni di giorni con conteggio di $count.
La parte di say include un map preso un prestito da un idioma ben noto, quindi va letto da destra a sinistra:

  1. si ordinano le chiavi (nomi mnemonici dei giorni della settimana) dell'hash del mese corrente;
  2. si crea una stringa con il conteggio di ripetizioni del giorno specificato e del suo nome mnemonico (parte map);
  3. si effettua un join di tutte le stringhe così costruite.

E quindi si stampa il tutto, con risultato analogo a quello del caso precedente.