2013-03-10 14 views
8

Quando mi è sembrato di capire a cosa serve il ritorno in Haskell, ho provato a giocare con diverse alternative e sembra che il ritorno non solo possa essere usato in qualsiasi punto della catena monade, ma può anche essere esclusi completamenteCosa c'è di speciale nella parola chiave 'return'

*Main> Just 9 >>= \y -> (Just y) >>= \x -> return x 
Just 9 

*Main> Just 9 >>= \y -> (return y) >>= \x -> (Just y) 
Just 9 

*Main> Just 9 >>= \y -> (Just y) >>= \x -> (Just x) 
Just 9 

Anche se tralascio di ritorno nel mio istanze, ho solo avvertimento ...

data MaybeG a = NothingG | JustG a deriving Show 
instance Monad MaybeG where 
    -- return x = JustG x 
     NothingG >>= f = NothingG 
     JustG x >>= f = f x 
     fail _ = NothingG 

Monad.hs:3:10: 
    Warning: No explicit method nor default method for `return' 
    In the instance declaration for `Monad MaybeG' 

e posso ancora usare la monade

*Main> JustG 9 >>= \y -> (JustG 11) >>= \x -> (JustG y) 
JustG 9 

*Main> JustG 9 >>= \y -> (NothingG) >>= \x -> (JustG y) 
NothingG 

Quindi, cosa c'è di così speciale nella parola chiave return? Si tratta di casi più complessi in cui non posso ometterlo? O perché questo è il modo "giusto" di fare le cose anche se possono essere fatte in modo diverso?

UPDATE: .. o un'altra alternativa, ho potuto definire mio costruttore valore monadico

finallyMyLastStepG :: Int -> MaybeG Int 
finallyMyLastStepG a = JustG a 

e produrre un'altra variante della stessa catena (con lo stesso risultato)

*Main> JustG 9 >>= \y -> (JustG 11) >>= \x -> (finallyMyLastStepG y) 
JustG 9 
+6

'return' non è una parola chiave. E sì, non fa il flusso di controllo come la parola chiave con lo stesso nome nella maggior parte dei linguaggi di programmazione imperativi, quindi il 'return ...' in 'do {pippo; ritorno ...; quux} 'è ridondante. – delnan

+0

@delnan, il problema è quando leggo tutto sulle monadi, il "ritorno" nell'ultima riga sembrava il requisito. Ma nella mia terza linea l'ho sostituito con la creazione diretta di valore monadico e l'Haskell va bene con questo. – Maksee

+3

@Maksee: avere un ritorno sull'ultima riga non è un requisito. Accade solo che per la monade Forse, l'operazione di bind contenga una costruzione di una nuova monade semplice. In genere non è il caso; considera, per esempio, la monade dell'identità. Non chiama 'return' su qualcosa in' bind'. –

risposta

10

Sospetto che tu abbia frainteso cosa significa "ritorno" nel contesto di una monade in Haskell. il ritorno è una funzione che accetta un a e restituisce un "a" impacchettato, ovvero l'istanza più semplice possibile della monade. In altre lingue è spesso chiamato Unit. Non è il "flusso di controllo" return che vedi nelle lingue simili a C.

Quindi nel tuo esempio del Maybe Monade, abbiamo il ritorno definito come una funzione che prende in un a e restituisce un Maybe a:

return :: a -> Maybe a 

E che cosa fa? se si dà x, ti dà indietro Just x:

E ora è possibile utilizzare return come abbreviazione quando hai bisogno di quella funzione, piuttosto che scrivere fuori:

\x -> Just x 

Si chiama return perché quando scrivi le monadi nella notazione do, sembra come si farebbe in un linguaggio simile a C.

+0

Ok, \ x -> (Solo x) nella mia terza riga fa esattamente la stessa cosa, restituisce il valore monadico – Maksee

+0

Si chiama 'Unità'? AFAIK 'Unità' come nome alternativo per la tupla vuota (o più astrattamente/generalmente/correttamente, il tipo con un solo valore). – delnan

+0

@Maksee: corretto. –

24

Quindi, cosa c'è di così speciale nella parola chiave return?

In primo luogo, è returnnon una parola chiave in Haskell. È una funzione sovraccaricata.

Il suo tipo è dato da:

class Monad m where 
    -- | Sequentially compose two actions, passing any value produced 
    -- by the first as an argument to the second. 
    (>>=)  :: m a -> (a -> m b) -> m b 

    -- | Inject a value into the monadic type. 
    return  :: a -> m a 

Così si vede che return è una funzione che, dato un valore di tipo a, restituisce un nuovo valore di tipo m a, dove m è un certo tipo che è un'istanza di Monad. Tali tipi sono:

  • Monade []
  • Monade I0
  • Monade Maybe
  • Monade STM
  • Monade ((->) r)
  • Monade (Either e)
  • Monade (ST s)

e molti altri ancora. Le istanze di 'Monad' dovrebbe soddisfare le seguenti leggi:

> return a >>= k == k a 
> m >>= return == m 
> m >>= (\x -> k x >>= h) == (m >>= k) >>= h 

L'implementazione di una funzione a -> m a è abbastanza facile da indovinare. Ecco la definizione per le monadi più comuni:

Liste:

return x = [x] 

Forse

return x = Just x 

Così si vede che la return è una funzione di overload che "ascensori" un valore in un involucro monadico. Puoi quindi usarlo ovunque tu possa usare la sua definizione. Per esempio.

Prelude> 1 : return 2 
[1,2] 

o in the do notion (utile quando chaining expressions).

> do v <- return 7 ; return v :: Maybe Int 
Just 7 

La vera ragione per usare un monadica return è quando la composizione di più valori in qualche monade:

Prelude> do x <- return 1 ; y <- return 2 ; return (x + y) :: Maybe Int 
Just 3 
Prelude> do x <- Nothing ; y <- return 2 ; return y 
Nothing 

Nell'ultima dichiarazione si vede come la catena di corto circuito, una volta colpito un valore zero per la data monade In questo caso Nothing.

Riepilogo: return è una funzione sovraccaricata che eleva un valore in un wrapper monadico. Lo usi quando hai bisogno di alzare i valori. È non una parola chiave control-flow, come nelle lingue imperative.

+0

Ok, vedo che applicare "parola chiave" a "ritorno" è sbagliato, ma soprattutto la mia domanda era se dovessi usarla sempre o se potessi tranquillamente usare l'altro modo di fare lo stesso. In altre parole visto che lo stesso risultato può essere restituito in molti altri modi, perché dovrei seguire la convenzione? – Maksee

+0

Quindi, posso sostituire l'ultimo esempio con _do x <- (Solo 1); y <- (Solo 2); (Just (x + y)) _ e ottieni lo stesso risultato senza usare "return" – Maksee

+0

@Maksee: Sì, fa la stessa cosa. – Vitus

1

Il commento di Mike Hartl mi ha portato nella giusta direzione, anche se non era così formale, quindi ho appena postato la mia comprensione finale di ciò che rende così speciale l'operatore di "ritorno".

Qualsiasi classe di tipo elenca l'operatore supportato e ci sono funzioni che possono funzionare solo in questo contesto di classe (imposto tramite il simbolo di costrizione di classe =>).Quindi, ad esempio filterM signature

filterM :: Monad m => (a -> m Bool) -> [a] -> m [a] 

ci mostra che può essere utilizzato solo in contesto monadico. La magia è che nel corpo questa funzione è libera di usare qualsiasi operatore della classe (>> = e restituire per Monad) e se un'istanza (ad esempio il mio MaybeG) manca di un metodo (ritorno nel mio caso) allora la funzione può fallire. Così, quando il ritorno è lì

> filterM (\x -> JustG (x > 0)) [2, 1, 0, -1] 
JustG [2,1] 

e quando è commentato (vedi il mio attuazione MaybeG nella questione)

> filterM (\x -> JustG (x > 0)) [2, 1, 0, -1] 

*** Exception: Monad.hs:3:10-21: No instance nor default method for class operation GHC.Base.return 

così imlementation di qualsiasi operatore (ritorno nel caso in cui monade) è necessario se uno dei piani utilizzare l'istanza con funzioni che funzionano con questo vincolo di classe (monad in questo caso).

Penso che il mio incomprensione iniziale fosse dovuto al fatto che la maggior parte dei tutorial spiega catene monadiche senza contesto polimorfo (ad hoc). Questo contesto, secondo me, rende le monadi più potenti e riutilizzabili.

+2

Anche quando conosci l'istanza 'Monad' che stai usando, potresti non sapere come è implementata, quindi devi usare' return' per alzare un valore. Ad esempio, come faresti a sollevare un valore in 'IO' senza' return'? – pat

Problemi correlati