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;
};