Najpierw rozróżnijmy uczenie się abstrakcyjnych pojęć i uczenie się ich konkretnych przykładów .
Nie zajdziesz daleko, ignorując wszystkie konkretne przykłady, z tego prostego powodu, że są one wszechobecne. W rzeczywistości abstrakcje istnieją w dużej części, ponieważ jednoczą rzeczy, które i tak byś robił z konkretnymi przykładami.
Same abstrakcje są z pewnością przydatne , ale nie są natychmiast potrzebne. Dość daleko można całkowicie zignorować abstrakcje i bezpośrednio użyć różnych typów. W końcu będziesz chciał je zrozumieć, ale zawsze możesz do nich wrócić później. W rzeczywistości mogę prawie zagwarantować, że jeśli to zrobisz, po powrocie do niego uderzysz się w czoło i zastanawiasz się, dlaczego spędziłeś tyle czasu na robieniu rzeczy zamiast korzystania z wygodnych narzędzi ogólnego zastosowania.
Weź Maybe a
jako przykład. To tylko typ danych:
data Maybe a = Just a | Nothing
To wszystko samo z siebie; jest to wartość opcjonalna. Albo masz „tylko” coś w rodzaju a
, albo nie masz nic. Załóżmy, że masz jakąś funkcję wyszukiwania, która powraca Maybe String
do reprezentowania szukania String
wartości, która może nie być obecna. Więc dopasuj wzór do wartości, aby zobaczyć, która to jest:
case lookupFunc key of
Just val -> ...
Nothing -> ...
To wszystko!
Naprawdę, nic więcej nie potrzebujesz. Żadnych Functor
s, Monad
ani nic innego. Wyrażają one typowe sposoby używania Maybe a
wartości ... ale są to tylko idiomy, „wzorce projektowe”, jakkolwiek chcesz to nazwać.
Jedynym miejscem, którego tak naprawdę nie można całkowicie uniknąć, jest IO
tajemnicza czarna skrzynka, więc nie warto próbować zrozumieć, co to znaczy jako coś takiego Monad
.
W rzeczywistości oto ściągawka na wszystko, o czym naprawdę musisz wiedzieć IO
na razie:
Jeśli coś ma typ IO a
, oznacza to, że jest to procedura , która coś robi i wyrzuca a
wartość.
Gdy masz blok kodu za pomocą do
notacji, napisz coś takiego:
do -- ...
inp <- getLine
-- etc...
... oznacza wykonanie procedury po prawej stronie <-
i przypisanie wyniku do nazwy po lewej stronie.
Natomiast jeśli masz coś takiego:
do -- ...
let x = [foo, bar]
-- etc...
... oznacza przypisanie wartości wyrażenia zwykłego (nie procedury) po prawej =
stronie nazwy po lewej stronie.
Jeśli umieścisz tam coś bez przypisania wartości, na przykład:
do putStrLn "blah blah, fishcakes"
... oznacza wykonanie procedury i zignorowanie wszystkiego, co zwróci. Niektóre procedury mają ten typ IO ()
- ()
typ jest rodzajem symbolu zastępczego, który nic nie mówi, więc oznacza to, że procedura coś robi i nie zwraca wartości. Coś jak void
funkcja w innych językach.
Wykonanie tej samej procedury więcej niż raz może dać różne wyniki; taki jest pomysł. Dlatego nie ma możliwości „usunięcia” IO
wartości, ponieważ coś w niej IO
nie jest wartością, jest to procedura uzyskania wartości.
Ostatni wiersz w do
bloku musi być prostą procedurą bez przypisania, gdzie wartość zwracana tej procedury staje się wartością zwracaną dla całego bloku. Jeśli chcesz, aby wartość zwracana używała już przypisanej wartości, return
funkcja przyjmuje zwykłą wartość i daje ci procedurę braku działania, która zwraca tę wartość.
Poza tym nie ma w tym nic specjalnego IO
; procedury te same w sobie są zwykłymi wartościami i można je przekazywać i łączyć na różne sposoby. Tylko wtedy, gdy są wykonywane w do
bloku wywołanym przez main
coś, co robią.
A więc w czymś takim, jak ten całkowicie nudny, stereotypowy przykładowy program:
hello = do putStrLn "What's your name?"
name <- getLine
let msg = "Hi, " ++ name ++ "!"
putStrLn msg
return name
... możesz to odczytać jak imperatywny program. Definiujemy procedurę o nazwie hello
. Po wykonaniu najpierw wykonuje procedurę drukowania wiadomości z pytaniem o twoje imię; następnie wykonuje procedurę, która odczytuje wiersz danych wejściowych i przypisuje wynik do name
; następnie przypisuje wyrażenie do nazwy msg
; następnie drukuje wiadomość; następnie zwraca nazwę użytkownika jako wynik całego bloku. Ponieważ name
jest to String
, oznacza hello
to, że procedura zwraca a String
, więc ma typ IO String
. A teraz możesz wykonać tę procedurę w innym miejscu, tak jak to się dzieje getLine
.
Pfff, monady. Kto ich potrzebuje?