La variabile di default di Perl è simile a una monade FP?

7

Mi sono messo in testa alle monadi nella programmazione funzionale e sembra vedere una certa comunanza tra la variabile di default di Perl $_ e le monadi FP.

È vero? Ci sono delle somiglianze, se non perché no? Forse questo mi permetterà di ingannare le monadi una volta per tutte capendo perché l'uso di $_ non è monad like!

La mia comprensione (in questa fase vaga) di una monade è che un aspetto è che può essere usato per concatenare i comandi in modo che l'output di uno sia magicamente l'input del successivo.

Quindi ad esempio questo perl:

while (<STDIN>) {
   chomp;
   s/Hello/Goodbye/;
   print;
}

Scatta dallo stdin, rimuove lo spazio bianco (chomp) sostituisce "Ciao" con "Arrivederci" e stampa il risultato. Ognuno di questi comandi utilizza il valore nella variabile implicita $_ e restituisce il risultato in esso.

Lo stesso codice ma che mostra la variabile implicita usa:

while ($_ = <STDIN>){
    chomp $_;
    $_ =~ s/Hello/Goodbye/;
    print $_;
}

Quindi correggimi se sbaglio, ma mi sembra molto simile a un aspetto delle monadi.

    
posta Kevin Pluck 27.10.2015 - 15:01
fonte

2 risposte

5

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;
};
    
risposta data 28.10.2015 - 00:04
fonte
2

Bene, non conosco Perl, ma quel codice mi ricorda% monete di% co_de. Quindi, ecco il mio tentativo di imitarlo in Haskell:

{-# LANGUAGE OverloadedStrings #-}

import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.State
import Data.Text.Lazy (Text)
import qualified Data.Text.Lazy as T
import qualified Data.Text.Lazy.IO as TextIO

main :: IO ()
main = flip evalStateT "" $ do
  stdin <- lift TextIO.getContents
  forLinesM_ stdin $ do
    modify T.strip                        -- chomp
    modify (T.replace "Hello" "Goodbye")  -- s/Hello/Goodbye/
    get >>= (lift . TextIO.putStrLn)      -- print 

-- | Auxiliary function to loop over the lines of a 'Text', putting
-- each line into the implicit state of the 'StateT' monad transformer.
forLinesM_ :: Monad m => Text -> StateT Text m b -> StateT Text m ()
forLinesM_ text body =
    forM_ (T.lines text) $ \line -> do
      put line
      body

Il State monad richiede che il tipo di stato sia uniforme in tutto il calcolo, quindi suppongo che sia piuttosto più restrittivo della variabile implicita Perl.

Trovo il codice sopra criptico. Che cosa fa Perl con variabili implicite, Haskell fa con la funzione o la composizione monadica. Quindi un programma Haskell migliore per questo sarebbe:

{-# LANGUAGE OverloadedStrings #-}

import Data.Text.Lazy (Text)
import qualified Data.Text.Lazy as T
import qualified Data.Text.Lazy.IO as TextIO

main :: IO ()
main = TextIO.interact go
  -- The '.' operator is right-to-left function composition, so 
  -- this reads from bottom to top:
  where go = 
       -- Join the lines back together
         T.unlines

       -- Replace strings in each line  
       . map (T.replace "Hello" "Goodbye")

       -- trim whitespace from each line
       . map T.strip                    

       -- Split input into lines
       . T.lines  

La composizione delle funzioni assomiglia alle pipe Unix più delle variabili implicite Perl.

    
risposta data 12.11.2015 - 02:15
fonte

Leggi altre domande sui tag