In primo luogo distinguiamo tra l'apprendimento dei concetti astratti e l'apprendimento di esempi specifici di essi.
Non andrai molto lontano ignorando tutti gli esempi specifici, per la semplice ragione che sono completamente onnipresenti. In effetti, 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 andare abbastanza lontano ignorando completamente le astrazioni e semplicemente usando direttamente i vari tipi. Ti consigliamo di capirli alla fine, ma puoi sempre tornarci più tardi. In effetti, posso quasi garantire che se lo fai, quando torni ad esso ti schiaffeggerai la fronte e mi chiederò perché hai passato tutto quel tempo a fare le cose nel modo più duro invece di usare i convenienti strumenti di uso generale.
Prendi Maybe a
come esempio. È solo un tipo di dati:
data Maybe a = Just a | Nothing
È tutto fuorché auto-documentante; è un valore opzionale. O hai "solo" qualcosa di tipo a
o non hai niente. Supponiamo che tu abbia una funzione di ricerca di qualche tipo, che ritorna Maybe String
per rappresentare la ricerca di un String
valore che potrebbe non essere presente. Quindi modellare la corrispondenza sul valore per vedere quale è:
case lookupFunc key of
Just val -> ...
Nothing -> ...
È tutto!
Davvero, non c'è nient'altro di cui hai bisogno. Non Functor
s o Monad
s o qualsiasi altra cosa. Esprimono modi comuni di usare i Maybe a
valori ... ma sono solo modi di dire, "schemi di progettazione", qualunque cosa tu voglia chiamare.
L'unico posto in cui non puoi assolutamente evitarlo è IO
, ma è comunque una misteriosa scatola nera, quindi non vale la pena provare a capire cosa significa come Monad
o qualunque cosa.
In effetti, ecco un cheat sheet per tutto ciò che devi veramente sapere IO
per ora:
Se qualcosa ha un tipo IO a
, significa che è una procedura che fa qualcosa e sputa fuori un a
valore.
Quando hai un blocco di codice usando la do
notazione, scrivi qualcosa del genere:
do -- ...
inp <- getLine
-- etc...
... significa eseguire la procedura a destra di <-
e assegnare il risultato al nome a sinistra.
Considerando che se hai qualcosa del genere:
do -- ...
let x = [foo, bar]
-- etc...
... significa assegnare il valore dell'espressione semplice (non una procedura) a destra del =
nome a sinistra.
Se metti qualcosa lì senza assegnare un valore, in questo modo:
do putStrLn "blah blah, fishcakes"
... significa eseguire una procedura e ignorare tutto ciò che restituisce. Alcune procedure hanno il tipo IO ()
--il ()
tipo è una sorta di segnaposto che non dice nulla, perché così, significa che la procedura non qualcosa e non restituisce un valore. Un po 'come una void
funzione in altre lingue.
L'esecuzione della stessa procedura più di una volta può dare risultati diversi; è un po 'l'idea. Questo è il motivo per cui non c'è modo di "rimuovere" il IO
da un valore, perché qualcosa in IO
non è un valore, è una procedura per ottenere un valore.
L'ultima riga in un do
blocco deve essere una procedura semplice senza assegnazione, in cui il valore restituito di quella procedura diventa il valore restituito per l'intero blocco. Se si desidera che il valore restituito utilizzi un valore già assegnato, la return
funzione accetta un valore semplice e fornisce una procedura non operativa che restituisce quel valore.
A parte questo, non c'è niente di speciale in merito IO
; queste procedure sono in realtà valori semplici e puoi passarle e combinarle in modi diversi. È solo quando vengono eseguiti in un do
blocco chiamato da qualche parte main
che fanno qualsiasi cosa.
Quindi, in qualcosa di simile a questo programma di esempio completamente noioso e stereotipato:
hello = do putStrLn "What's your name?"
name <- getLine
let msg = "Hi, " ++ name ++ "!"
putStrLn msg
return name
... puoi leggerlo proprio come un programma imperativo. Stiamo definendo una procedura denominata hello
. Quando eseguito, per prima cosa 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
; quindi stampa il messaggio; quindi restituisce il nome dell'utente come risultato dell'intero blocco. Poiché name
è a String
, ciò significa che hello
è una procedura che restituisce a String
, quindi ha tipo IO String
. E ora puoi eseguire questa procedura altrove, proprio come viene eseguita getLine
.
Pfff, monadi. Chi ne ha bisogno?