Quanto sono importanti i concetti avanzati di Haskell come Monade e Funzionalità Applicative per la maggior parte delle attività di programmazione di routine?

16

Ho letto il libro Learn You a Haskell fino al punto in cui introducono Monads e cose come Just a. Sono così poco intuitivi per me che ho voglia di arrendermi cercando di impararlo.

Ma voglio davvero provarci.

Mi chiedo se posso almeno evitare i concetti avanzati per un po 'e iniziare a usare le altre parti del linguaggio per svolgere molti compiti pratici con le parti più basilari di Haskell, cioè funzioni, IO standard, gestione dei file, interfaccia con database e simili.

È un approccio ragionevole all'apprendimento dell'uso di Haskell?

    
posta dan 26.07.2011 - 05:04
fonte

3 risposte

16

Per prima cosa distinguiamo tra l'apprendimento dei concetti astratti e l'apprendimento di esempi specifici di essi.

Non hai intenzione di andare molto lontano ignorando tutti gli esempi specifici, per la semplice ragione che sono assolutamente onnipresenti. Infatti, le astrazioni esistono in gran parte perché unificano le cose che faresti comunque con gli esempi specifici.

Le astrazioni stesse, d'altra parte, sono certamente utili , ma non sono immediatamente necessarie. Puoi ottenere abbastanza lontano ignorando completamente le astrazioni e semplicemente usando i vari tipi direttamente. Alla fine vorrai capirli, ma puoi sempre tornare indietro più tardi. In effetti, posso quasi garantire che, se lo fai, quando torni indietro ti schiaffi la fronte e ti chiedi perché hai passato tutto quel tempo a fare le cose nel modo più duro invece di usare i comodi strumenti generali.

Prendi Maybe a come esempio. È solo un tipo di dati:

data Maybe a = Just a | Nothing

È tutto fuorché autodocumentante; è un valore facoltativo. O hai "solo" qualcosa di tipo a , o non hai nulla. Supponiamo che tu abbia una funzione di ricerca di qualche tipo, che restituisca Maybe String per rappresentare la ricerca di un valore String che potrebbe non essere presente. In questo modo, il modello corrisponde al valore per vedere quale è:

case lookupFunc key of
    Just val -> ...
    Nothing  -> ...

Questo è tutto!

In realtà, non c'è nient'altro di cui hai bisogno. Nessun Functor s o Monad s o altro. Quelli esprimono modi comuni di utilizzare valori di Maybe a ... ma sono solo espressioni idiomatiche, "schemi di progettazione", qualsiasi cosa tu voglia chiamare.

L'unico posto in cui non puoi davvero evitarlo completamente è con IO , ma è comunque una misteriosa scatola nera, quindi non vale la pena provare a capire cosa significa Monad o altro.

In effetti, ecco un cheat sheet per tutti voi veramente che devono sapere su IO per ora:

  • Se qualcosa ha un tipo IO a , significa che è una procedura che fa qualcosa e sputa un valore a .

  • Quando hai un blocco di codice usando la notazione do , scrivi qualcosa del tipo:

    do -- ...
       inp <- getLine
       -- etc...
    

    ... significa eseguire la procedura alla destra di <- , e assegnare il risultato al nome sulla sinistra.

  • Se invece hai qualcosa di simile:

    do -- ...
       let x = [foo, bar]
       -- etc...
    

    ... significa assegnare il valore dell'espressione semplice (non una procedura) alla destra di = al nome sulla sinistra.

  • Se metti qualcosa lì senza assegnare un valore, come questo:

    do putStrLn "blah blah, fishcakes"
    

    ... significa eseguire una procedura e ignorare qualsiasi cosa ritorni. Alcune procedure hanno il tipo IO () - il tipo () è una specie di segnaposto che non dice nulla, quindi significa solo che la procedura fa qualcosa e non restituisce un valore. Un po 'come una funzione void in altre lingue.

  • L'esecuzione della stessa procedura più di una volta può dare risultati diversi; è una specie di idea. Questo è il motivo per cui non c'è modo di "rimuovere" IO da un valore, perché qualcosa in IO non è un valore, è una procedura per ottenere un valore.

  • L'ultima riga in un blocco do deve essere una semplice procedura senza assegnazione, in cui il valore di ritorno di tale procedura diventa il valore di ritorno per l'intero blocco. Se si desidera che il valore di ritorno utilizzi un valore già assegnato, la funzione return assume un valore normale e fornisce una procedura non operativa che restituisce tale valore.

  • Oltre a questo, non c'è niente di speciale su IO ; queste procedure sono in realtà semplici valori, e puoi passarle intorno e combinarle in diversi modi. È solo quando vengono eseguiti in un blocco do chiamato da qualche parte da main che fanno qualsiasi cosa.

Quindi, in qualcosa come questo programma di esempio stereotipato e noiosissimo:

hello = do putStrLn "What's your name?"
           name <- getLine
           let msg = "Hi, " ++ name ++ "!"
           putStrLn msg
           return name

... puoi leggerlo come un programma imperativo. Stiamo definendo una procedura denominata hello . Quando viene eseguito, prima esegue una procedura per stampare un messaggio che chiede il tuo nome; successivamente esegue una procedura che legge una riga di input e assegna il risultato a name ; quindi assegna un'espressione al nome msg ; poi stampa il messaggio; quindi restituisce il nome dell'utente come risultato dell'intero blocco. Poiché name è un String , ciò significa che hello è una procedura che restituisce un String , quindi ha tipo IO String . E ora puoi eseguire questa procedura altrove, proprio come si esegue getLine .

Pfff, monadi. Chi ne ha bisogno?

    
risposta data 26.07.2011 - 06:25
fonte
9

How important are Haskell's advanced concepts like Monads and Applicative Functors for most routine programming tasks?

Sono essenziali. Senza di loro, è fin troppo facile usare Haskell come un altro linguaggio imperativo, e anche se Simon Peyton Jones una volta chiamò Haskell, "il linguaggio di programmazione imperativo più raffinato del mondo", ti stai ancora perdendo la parte migliore.

Monade, Trasformazioni di Monade, Monoidi, Funcici, Funzionalità applicative, Controversie tra loro, Frecce, Categorie, ecc. sono modelli di design. Una volta che si adatta alla specifica cosa che stai facendo in uno di essi, sei in grado di attingere a un'enorme ricchezza di funzioni che sono scritte in modo astratto. Queste classi tipo non sono semplicemente lì per confondervi, sono lì per rendere la vita più facile e per renderti più produttivo. Sono, a mio parere, la più grande ragione per la concisione del buon codice Haskell.

    
risposta data 15.01.2012 - 08:30
fonte
7

È strettamente necessario se vuoi solo far funzionare qualcosa? No.

È necessario se vuoi scrivere bei programmi idiomatici di Haskell? Assolutamente.

Quando si programma in Haskell aiuta a tenere a mente due cose:

  1. Il sistema dei tipi è lì per aiutarti. Se componi il tuo programma partendo da un codice altamente polimorfo di classe tipografica, molto spesso puoi entrare nella meravigliosa situazione in cui il tipo di funzione che desideri ti guiderà verso l'implementazione corretta (una sola!) .

  2. Le varie librerie disponibili sono state sviluppate usando il principio # 1.

Quindi, quando scrivo un programma in Haskell, inizio scrivendo i tipi. Quindi ricomincio e scrivo alcuni tipi più generali (e se possono essere esempi di typeclass esistenti, tanto meglio). Poi scrivo alcune funzioni che mi danno i valori dei tipi che voglio. (Poi gioco con loro in ghci e magari scrivo alcuni test quickCheck.) E alla fine li incollo tutti insieme in main .

È possibile scrivere sul brutto Haskell che fa funzionare qualcosa. Ma c'è molto altro da imparare una volta raggiunto lo stadio.

Buona fortuna!

    
risposta data 26.07.2011 - 23:59
fonte

Leggi altre domande sui tag