Zaawansowany czysto funkcyjny język programowania


Deklaratywny, statycznie typowany kod.

primes = filterPrime [2..]
  where filterPrime (p:xs) =
          p : filterPrime [x | x <- xs, x `mod` p /= 0]

Wypróbuj Haskell!

Cechy języka

Statyczne typowanie

Każde wyrażenie w języku Haskell posiada typ, który zostaje określony w czasie kompilacji. Typy używane w przepływie danych muszą być zgodne. Jeśli nie są program zostanie prezz kompilator odrzucony. Typy są nie tylko formą gwarancji, ale także sposobem wyrażania konstrukcji programu.

Rozwiń

Wszystkie wartości w jęyku Haskell posiadają typ:

char = 'a'    :: Char
int = 123     :: Int
fun = isDigit :: Char -> Bool

Musisz przekazać do funkcji wartości właściwego typu albo kompilator zgłosi błąd:

Type error
isDigit 1

Możesz dekodować bajty do postaci tekstu:

bytes = Crypto.Hash.SHA1.hash "hello" :: ByteString
text = decodeUtf8 bytes               :: Text

Jednak nie możesz dekodować testu, który jest wektorem wskaźników Unicode:

Type error
doubleDecode = decodeUtf8 (decodeUtf8 bytes)

Czysto funkcjonalny

Każda funkcja w języku Haskell jest funkcją w znaczeniu matematycznym (tzw. "czysta funkcja"). Nawet operacje wejścia-wyjścia posiadające efekt uboczny takie są, jedynie opisują co program robi i tworzą czysty kod. W języku nie ma wyrażeń ani instrukcji lecz istnieją wyrażenia, które nie mogą zmieniać zmiennych (lokalnych ani globalnych) ani stanu (takiego jak czas) ani wartości losowych.

Rozwiń

Poniższa funkcja przyjmuje i zwraca typ całkowity (Int). Nie może wywołać efektu ubocznego, nie może zmienić żadnego ze swoich argumentów.

square :: Int -> Int
square x = x * x

Poniższa konkatenacja literałów typu znakowego jest poprawna:

"Hello: " ++ "World!" 

Poniższa konkatenacja jest błędna:

Type error
"Name: " ++ getLine

Ponieważ getLine jest typu IO String a nie typu String, tak jak literał "Name: ". Więc system typów wymusza rezygnację z mieszania typów oraz zachowanie czystości kodu pod tym względem.

Wnioskowanie typu

Nie musisz jawnie wskazywać każdego typu w języku Haskell. Typy są wnioskowane dwukierunkowo. Jednak możesz wskazać typy jeśli tak zdecydujesz lub nakazać kompilatorowi wymienić je w podręcznej dokumentacji.

Rozwiń

Ten przykład posiada sygnaturę typu dla każdego wiązania:

main :: IO ()
main = do line :: String <- getLine
          print (parseDigit line)
  where parseDigit :: String -> Maybe Int
        parseDigit ((c :: Char) : _) =
          if isDigit c
             then Just (ord c - ord '0')
             else Nothing

Jednak możesz napisać po prostu:

main = do line <- getLine
          print (parseDigit line)
  where parseDigit (c : _) =
          if isDigit c
             then Just (ord c - ord '0')
             else Nothing

Możesz także używać wnioskowania typu by unikać straty czasu na wyjaśnianie oczywistych zamiarów:

do ss <- decode "[\"Hello!\",\"World!\"]"
   is <- decode "[1,2,3]"
   return (zipWith (\s i -> s ++ " " ++ show (i + 5)) ss is)
 => Just ["Hello! 6","World! 7"]

Typy prezkazują parserowi istotną informację, jej brak nie jest akceptowany:

do ss <- decode "[1,2,3]"
   is <- decode "[null,null,null]"
   return (zipWith (\s i -> s ++ " " ++ show (i + 5)) ss is)
 => Nothing

Wielowątkowość

Haskell nadaje się również do programowania współbieżnego ze względu na jego jawną obsługę efektów. Flagowy kompilator, GHC, wyposażony jest w wysokiej klasy wielowątkowy odśmieciacz i lekki współbieżną bibliotekę zawierającą wiele przydatnych wielowątkowo-bezpiecznych prymitywów i abstrakcji.

Rozwiń

Łatwość uruchamiania wątków i komunikacji ze standardową biblioteką:

main = do
  done <- newEmptyMVar
  forkIO (do putStrLn "I'm one thread!"
             putMVar done "Done!")
  second <- forkIO (do threadDelay 100000
                       putStrLn "I'm another thread!")
  killThread second
  msg <- takeMVar done
  putStrLn msg

Możesz używać asynchronicznego API dla wątków:

do a1 <- async (getURL url1)
  a2 <- async (getURL url2)
  page1 <- wait a1
  page2 <- wait a2
  ...

Atomowe wątki z pamięcią transakcyjną:

transfer :: Account -> Account -> Int -> IO ()
transfer from to amount =
  atomically (do deposit to amount
                 withdraw from amount)

Atomowe transakcje muszą być powtarzalne:

Type error
main = atomically (putStrLn "Hello!")

Leniwe wartościowanie

Funkcje nie wyznaczają wartości zwracanych od razu. Oznacza to, że programy mogą być tworzone bez obawy, że coś zostanie obliczone niepotrzebnie i nie wykorzystane. Leniwe wartościowanie skutkuje łatwością tworzenia przejrzystego kodu z zachowaniem wydajności rozwiązania.

Rozwiń

Definiuj łatwo struktury kontrolne:

when p m = if p then m else return ()
main = do args <- getArgs
          when (null args)
               (putStrLn "No args specified!") 

Jeśli zauważysz powtarzające wzory wyrażeń takich jak

if c then t else False

to możesz nadać im nazwę, np:

and c t = if c then t else False

i używać ich w taki sam sposób jak pierwotne wyrażenie.

Twórz reużywalny kod za pomocą komponowania go z leniwych funkcji. To całkiem naturalne by wyrażać dowolną funkcję (any) za pomocą reużywania funkcji map oraz or:

any :: (a -> Bool) -> [a] -> Bool
any p = or . map p

Reużywaj wzorców rekurencyjnych w map, filter, foldr, itp.

Pakiety

Wkład środowiska open source w rozwój języka Haskell jest bardzo aktywny z szerokim zakresem pakietów dostępnych na publicznych serwerach pakietów.

Rozwiń

Jest dostępnych darmowo 6,954 pakietów. Oto niektóre z nich:

bytestring Dane binarne base Pakiet podstawowy, IO, wątki
network Połączenia sieciowe text Tekst w Unicode
parsec Parser directory Obsługa plików i katalogów
hspec Testy inspirowane RSpec attoparsec Szybki parser
monad-logger Logowanie (zdarzeń) persistent Biblioteka ORM
template-haskell Meta-programowanie tar Archiwa Tar
snap Web framework time Data, czas, itp.
happstack Web framework yesod Web framework
containers Mapy, grafy, zbiory fsnotify notyfikacja zdarzeń w systemie plików
hint Interpreter Haskella unix UNIX bindings
SDL SDL binding OpenGL OpenGL graphics system
criterion Benchmarking pango Text rendering
cairo Cairo graphics statistics Statistical analysis
gtk Gtk+ library glib GLib library
test-framework Testing framework resource-pool Resource pooling
conduit Streaming I/O mwc-random High-quality randoms
QuickCheck Property testing stm Atomic threading
blaze-html Markup generation cereal Binary parsing/printing
xml XML parser/printer http-client HTTP client engine
zlib zlib/gzip/raw yaml YAML parser/printer
pandoc Markup conversion binary Serialization
tls TLS/SSL zip-archive Zip compression
warp Web server text-icu Text encodings
vector Vectors async Asyn concurrency
pipes Streaming IO scientific Arbitrary-prec. nums
process Launch processes aeson JSON parser/printer
dlist Difflists syb Generic prog.