Qual è lo scopo dei valori avvolti in Haskell?

6

Recentemente ho letto un articolo su Functional, Applicatives and Monads in Haskell e si conclude con queste affermazioni:

  • functors: si applica una funzione a un valore spostato utilizzando fmap o <$>
  • applicativi: si applica una funzione avvolta a un valore spostato utilizzando <*> o liftA
  • monadi: si applica una funzione che restituisce un valore avvolto, a un valore avvolto utilizzando >>= o liftM

Questo è tutto chiaro ma non capisco quale sia lo scopo del valore incapsulato . Funzionano come i decoratori in python o Optional in java? Apprezzerei molto un esempio pratico del mondo reale.

    
posta Adam Arold 24.11.2015 - 18:30
fonte

3 risposte

8

Il Optional di Java equivale essenzialmente alla% monade di hashell% co_de, quindi è un buon punto di partenza per capire come si comportano questi "valori avvolti".

Maybe , Functors e Applicatives sono modi per modellare un tipo di comportamento aggiuntivo che può modificare un tipo esistente. Monads prende un tipo e lo modifica in modo che possa modellare sia una una singola istanza o nessuna istanza di quel tipo. Maybe modifica un tipo in modo tale da modellare una sequenza di istanze del tipo specificato.

Ciascuno di questi typeclass fornisce un modo per applicare una determinata funzione al tipo avvolto, rispettando le modifiche apportate dal tipo di wrapping. Ad esempio, in Haskell,

Funtori

I Functional usano le funzioni List o fmap (nomi diversi per la stessa cosa):

  • <$> o fmap <$>

Questo prende una funzione e si applica agli elementi spostati

fmap (\x -> x + 1) (Just 1)   -- Applies (+1) to the inner value, returning (Just 2)
fmap (\x -> x + 1) Nothing    -- Applies (+1) to an empty wrapper, returning Nothing

fmap (\x -> x + 1) [1, 2, 3]  -- Applies (+1) to all inner values, returning [2, 3, 4]
(\x -> x + 1) <$> [1, 2, 3]   -- Same as above

applicativi

I candidati usano la funzione Functor f => (a -> b) -> f a -> f b :

  • <*> <*>

Questo richiede una funzione wrapped e la applica agli elementi spostati

(Just (\x -> x + 1)) <*> (Just 1) -- Returns (Just 2)
(Just (\x -> x + 1)) <*> Nothing  -- Returns Nothing
Nothing <*> (Just 1)              -- Returns Nothing
[(*2), (*4)] <*> [1, 2]           -- Returns [2, 4, 4, 8]

Monadi

Ci sono due funzioni rilevanti nella classe di caratteri Monad:

  • Applicative f => f (a -> b) -> f a -> f b return
  • Monad m => a -> m a (>>=) (pronunciato "bind")

La funzione Monad m => m a -> (a -> m b) -> m b non ha alcuna somiglianza con quella che potresti avere familiarità con i linguaggi in stile C. Prende un valore raw e non compresso e lo avvolge nel tipo monadico desiderato.

makeJust :: a -> Maybe a
makeJust x = return x

let foo = makeJust 10 -- returns (Just 10)

La funzione bind consente di scartare temporaneamente gli elementi interni di una Monade e passarli a una funzione che esegue un'azione che li avvolge di nuovo nella stessa monade. Questo può essere usato con la funzione return in casi banali:

[1, 2, 3, 4] >>= (\x -> return (x + 1))  -- Returns [2, 3, 4, 5]
(Just 1) >>= (\x -> return (x + 1))      -- Returns (Just 2)
Nothing >>= (\x -> return (x + 1))       -- Returns Nothing

Il punto in cui diventa interessante è quando si hanno delle funzioni da concatenare che non richiedono l'utilizzo di return . Mi piace usare return e getLine come esempi, che hanno le seguenti firme che usano% mon_putStrLn:

  • IO getLine
  • IO String putStrLn

Puoi chiamare queste funzioni in questo modo:

getLine >>= (\x -> putStrLn x) -- Gets a line from IO and prints it to the console
getLine >>= putStrLn -- With currying, this is the same as above

Potresti anche utilizzare la funzione String -> IO () per concatenare alcune operazioni insieme.

-- Reads a line from IO, converts to a number, adds 10 and prints it
getLine >>= (return . read) >>= (return . (+10)) >>= putStrLn . show
    
risposta data 24.11.2015 - 20:39
fonte
3

Questo articolo ha il pregio di essere molto visivo e intuitivo ... ma al costo di costruire intuizioni spesso false. I concetti di funtore, applicativo e monade non richiedono di avere valori "avvolti", in senso stretto.

Un buon esempio da considerare qui è il concetto di promises che sta ottenendo di recente la trazione (in particolare in Javascript). Una promessa, in questo senso, è un oggetto che funge da segnaposto per il valore risultante di un calcolo asincrono e di sfondo, come il recupero di alcuni dati da un servizio remoto. In aggiunta a ciò, funge da mediatore tra il calcolo asincrono e le funzioni che devono operare sui risultati previsti.

Le librerie di promessa di solito supportano un'API funzionale / monadica in cui è possibile "concatenare" una funzione su una promessa, che produce un'altra promessa che produce il risultato dell'applicazione di tale funzione al risultato della promessa originale.

Questo fornisce un vivido esempio del valore dell'interfaccia functor / monad, perché l'intero punto delle promesse è che ti permettono di dire quale funzione dovrebbe applicarsi al risultato di un'attività in background, prima che quell'attività abbia completato . Quando map una funzione su una promessa, il valore che la tua funzione dovrebbe applicare a potrebbe non essere stato ancora calcolato (e in effetti, se c'è un errore da qualche parte, potrebbe non essere mai calcolato).

Più in generale, puoi pensare a functor / applicative / monad come interfacce per gli oggetti mediator che si trovano tra le funzioni e gli argomenti e collegarli indirettamente in base ad una politica. Il modo più semplice per usare una funzione è chiamarlo con alcuni argomenti; ma se hai funzioni di prima classe, hai altre opzioni indirette: puoi fornire la funzione a un oggetto mediatore che controllerà se, quando e quante volte verrà chiamata la funzione e cosa fare con il suo risultato. Le promesse sono uno di questi esempi:

  1. Chiamano le funzioni a loro fornite quando il risultato di qualche attività in background è completato
  2. I risultati di tali funzioni vengono quindi trasferiti ad altre promesse che li stanno aspettando.
risposta data 24.11.2015 - 20:49
fonte
2

Bene Facoltativo è una monade (e quindi un funtore applicativo e un funtore), e in effetti molti tipi di contenitori sono monadici, e la gente di solito ha un'intuizione abbastanza buona per come funzionano le cose di contenimento come queste cose, come credo fai dalla tua domanda.

Tuttavia molte cose che normalmente non vengono considerate come contenitori possono essere anche monadi, applicativi o funtori, ad es. parser combinatori o IO o scrivere su un log. Un modo alternativo di guardarli è come calcoli in qualche contesto, ad esempio, mentre List è ovviamente un contenitore di valori, puoi anche considerare i calcoli nella monade List come rappresentante di calcoli non deterministici

Quello che sto portando a qui è che le monadi sono intrinsecamente più astratte degli esempi che stai cercando. sono letteralmente:

  • un costruttore di tipi
  • una funzione di ritorno / unità
  • e una funzione bind (o join e fmap)

che soddisfano le 3 leggi monad , non esiste un ulteriore principio unificante di quello (per una monade, sostituire con le funzioni rilevanti e leggi per Applicativo e Functional)

    
risposta data 24.11.2015 - 20:48
fonte

Leggi altre domande sui tag