Capisco perché vedi una somiglianza tra le monadi e la variabile $_ :
- un monad incapsula un valore in qualche contesto e consente di eseguire operazioni su quel valore.
- la variabile
$_ fa riferimento al contesto corrente. Le operazioni possono utilizzare questo contesto.
Quindi la parte comune è che esiste un valore, un contesto e alcune operazioni. Sfortunatamente, questo potrebbe descrivere tutta la programmazione. La variabile $_ non è come mi piace perché in realtà non ha alcuna proprietà di una monade. Ancora più importante, l'uso della variabile $_ non compone.
Un aspetto delle monadi è che possono essere definite da due operazioni: una per avvolgere un valore in un contesto (talvolta chiamato un costruttore, o un'operazione return ) e un'operazione bind per applicare un'azione a un valore. L'azione assegnata a bind prende un valore non compresso e restituisce di nuovo una monade del tipo atteso. È davvero importante che bind non restituisca un valore nudo, ma una monade. La monade è responsabile per decidere se, con quale frequenza e in quale ordine l'azione bind viene applicata al suo valore incapsulato, se presente. Questa flessibilità consente alle monadi di modellare le collezioni con qualsiasi numero di elementi e anche di controllare i costrutti del flusso come i condizionali.
In Perl, gli elenchi hanno per lo più un comportamento da monad. La loro costruzione viene tracciata in modo implicito nel contesto dell'elenco e la loro operazione bind è la map incorporata. Il contesto speciale di una lista è che tutti i valori nella lista sono ordinati. Esempi:
# the empty list
() #=> ()
# a list of one element
(1) #=> (1)
# a longer list
(1, 2, 3, 4) #=> (1, 2, 3, 4)
# identity
map { ($_) } (1, 2, 3) #=> (1, 2, 3)
# double each item
map { ($_, $_) } (1, 2, 3) #=> (1, 1, 2, 2, 3, 3)
Possiamo dimostrare che questo soddisfa le leggi della monade:
-
il costruttore è l'operazione neutra per il bind: data una lista @list , map { ($_) } @list è sempre uguale a @list e avvolge un valore $x in un elenco ($x) e quindi esegue il mapping una funzione f su di essa map { f($_) } ($x) ha lo stesso effetto di f($x) .
-
le azioni vincolanti soddisfano un'equivalenza specifica. Per un dato @list e tutte le (pure) funzioni f e g , queste due espressioni devono essere equivalenti:
map { g($_) } map { f($_) } @list
map { map { g($_) } f($_) } @list
Ora mettiamolo a confronto con operazioni come s/foo/bar/ che funzionano su $_ per impostazione predefinita. Abbiamo chiaramente un tipo di costruttore per $_ , ad es. for loops posiziona il valore in $_ per impostazione predefinita. E abbiamo un tipo di meccanismo di legame che consente a $_ di essere utilizzato da qualche operazione. Queste operazioni prendono il valore da $_ e in genere lo modificano sul posto. Tuttavia, le modifiche sul posto sono fondamentalmente in disaccordo con il comportamento monadico: l'operazione di bind prevede un'azione che accetta un valore non compresso e restituisce una monade. Con la modifica sul posto, dobbiamo restituire esattamente un valore. Non ci sono wrapping / unwrapping che si svolgono in modo esplicito o implicito.
Il punto in cui il risultato di ogni calcolo viene utilizzato dal passaggio successivo può sembrare monadico, ma è solo una programmazione imperativa. Essere una variabile stateful è tutto ciò che $_ è. Naturalmente, puoi modellare i calcoli con stato imperativo con una monade di stato, ma è più l'operatore ; che la variabile $_ .
A proposito, l'unico aspetto speciale di $_ è che le variabili *_ sono super-globali e si risolvono sempre nel pacchetto principale. La sintassi generale del codice di esempio può essere rispecchiata con qualsiasi altra variabile implicita, ad es. $::florp :
sub florpyreadwhile {
my ($fh, $body) = @_;
while ($::florp = <$fh>) {
$body->();
}
}
sub florpychomp {
return chomp $_[0] if @_;
return chomp $::florp;
}
sub florpysubstitute {
my ($re, $sub) = @_;
my $str_ref = (@_ >= 3) ? \$_[2] : \$::florp;
return $$str_ref =~ s/$re/$sub/;
}
sub florpyprint {
return print @_ if @_;
return print $::florp;
}
# using florpy sugar
florpyreadwhile \*STDIN => sub {
florpychomp;
florpysubstitute qr/Hello/ => 'Goodbye';
florpyprint;
};
# without florpy sugar
florpyreadwhile \*STDIN => sub {
florpychomp $::florp;
florpysubstitute qr/Hello/ => 'Goodbye', $::florp;
florpyprint $::florp;
};