Szukaj…


Uwagi

Logo Haskell

Haskell jest zaawansowanym, czysto funkcjonalnym językiem programowania.

Cechy:

  • Typy statyczne: Każde wyrażenie w języku Haskell ma typ określony w czasie kompilacji. Statyczne sprawdzanie typu to proces weryfikacji bezpieczeństwa typu programu na podstawie analizy tekstu programu (kodu źródłowego). Jeśli program przejdzie sprawdzanie typu statycznego, wówczas program ma zagwarantowany pewien zestaw właściwości bezpieczeństwa typu dla wszystkich możliwych danych wejściowych.
  • Czysto funkcjonalny : każda funkcja w Haskell jest funkcją w sensie matematycznym. Nie ma instrukcji ani instrukcji, tylko wyrażenia, które nie mogą mutować zmiennych (lokalnych lub globalnych) ani uzyskiwać dostępu do stanu, takich jak czas lub liczby losowe.
  • Współbieżny: jego flagowy kompilator, GHC, jest wyposażony w wysokowydajny równoległy moduł zbierający śmieci i lekką bibliotekę współbieżności zawierającą szereg użytecznych operacji podstawowych i abstrakcji.
  • Leniwa ocena: funkcje nie oceniają swoich argumentów. Opóźnia ocenę wyrażenia, dopóki nie jest potrzebna jego wartość.
  • Ogólne zastosowanie: Haskell jest zbudowany do użytku we wszystkich kontekstach i środowiskach.
  • Pakiety: Wkład Open Source w Haskell jest bardzo aktywny dzięki szerokiej gamie pakietów dostępnych na publicznych serwerach pakietów.

Najnowszym standardem Haskell jest Haskell 2010. Od maja 2016 r. Grupa pracuje nad kolejną wersją, Haskell 2020.

Oficjalna dokumentacja Haskell jest także obszernym i użytecznym źródłem. Świetne miejsce do znalezienia książek, kursów, samouczków, instrukcji, przewodników itp.

Wersje

Wersja Data wydania
Haskell 2010 10.10.2012
Haskell 98 2002-12-01

Witaj świecie!

Podstawowe „Witaj, świecie!” program w Haskell można wyrazić zwięźle w jednym lub dwóch wierszach:

main :: IO ()
main = putStrLn "Hello, World!"

Pierwszy wiersz to opcjonalna adnotacja typu, wskazująca, że main jest wartością typu IO () , reprezentującą akcję I / O, która „oblicza” wartość type () (czytaj „unit”; pusta krotka nie przekazuje żadnych informacji) oprócz wykonywania niektórych efektów ubocznych na świecie zewnętrznym (tutaj, drukowanie łańcucha na terminalu). Adnotacja tego typu jest zwykle pomijana w przypadku main ponieważ jest to jedyny możliwy typ.

Umieść to w pliku helloworld.hs i skompiluj za pomocą kompilatora Haskell, takiego jak GHC:

ghc helloworld.hs

Wykonanie skompilowanego pliku spowoduje wygenerowanie wyniku "Hello, World!" drukowane na ekranie:

./helloworld
Hello, World!

Alternatywnie, runhaskell lub runghc umożliwiają uruchomienie programu w trybie interpretowanym bez konieczności jego kompilacji:

runhaskell helloworld.hs

Zamiast kompilacji można również użyć interaktywnej REPL. Jest dostarczany z większością środowisk Haskell, takich jak ghci które są dostarczane z kompilatorem GHC:

ghci> putStrLn "Hello World!"
Hello, World!
ghci> 

Alternatywnie, załaduj skrypty do ghci z pliku używając load (lub :l ):

ghci> :load helloworld

:reload (lub :r ) przeładowuje wszystko w ghci:

Prelude> :l helloworld.hs 
[1 of 1] Compiling Main             ( helloworld.hs, interpreted )

<some time later after some edits>

*Main> :r
Ok, modules loaded: Main.

Wyjaśnienie:

Ten pierwszy wiersz jest sygnaturą typu, określającą typ main :

main :: IO ()

Wartości typu IO () opisują działania, które mogą wchodzić w interakcje ze światem zewnętrznym.

Ponieważ Haskell ma w pełni rozwinięty system typu Hindley-Milner, który pozwala na automatyczne wnioskowanie o typie, podpisy typów są technicznie opcjonalne: jeśli po prostu pominiesz main :: IO () , kompilator będzie mógł samodzielnie wnioskować o typie przez analizując definicję main . Jednak uważa się za zły styl, aby nie pisać podpisów typów dla definicji najwyższego poziomu. Powody obejmują:

  • Podpisy typów w Haskell są bardzo pomocną dokumentacją, ponieważ system typów jest tak wyrazisty, że często można zobaczyć, do czego nadaje się funkcja, po prostu patrząc na jej typ. Do tej „dokumentacji” można wygodnie uzyskać dostęp za pomocą narzędzi takich jak GHCi. I w przeciwieństwie do zwykłej dokumentacji, moduł sprawdzania typu kompilatora upewni się, że faktycznie odpowiada definicji funkcji!

  • Podpisy typów utrzymują błędy na poziomie lokalnym . Jeśli popełnisz błąd w definicji bez podania jej podpisu typu, kompilator może nie natychmiast zgłosić błąd, ale po prostu wywnioskować dla niego nonsensowny typ, z którym faktycznie sprawdza typ. Podczas korzystania z tej wartości może pojawić się tajemniczy komunikat o błędzie. Dzięki sygnaturze kompilator bardzo dobrze wykrywa błędy we właściwym miejscu.

Ta druga linia faktycznie działa:

main = putStrLn "Hello, World!"

Jeśli pochodzisz z języka imperatywnego, warto zauważyć, że tę definicję można również zapisać jako:

main = do {
   putStrLn "Hello, World!" ;
   return ()
   }

Lub równoważnie (Haskell ma parsowanie oparte na układzie, ale uważaj, aby nie mieszać tabulatorów i spacji w sposób niespójny, co dezorientuje ten mechanizm):

main = do
    putStrLn "Hello, World!"
    return ()

Każdy wiersz w do bloku stanowi część monadycznego (tutaj I / O) dla obliczenia, tak że cała do bloku przedstawia ogólne działanie składa się z tych podetapów przez połączenie ich w sposób specyficzny dla danego monadzie (I / O oznacza to po prostu wykonanie ich jeden po drugim).

do składnia jest sama w sobie cukier syntaktyczny dla monad, jak IO tutaj, a return to działanie nie-op produkuje swój argument bez wykonywania żadnych skutków ubocznych lub dodatkowych obliczeń, które mogą być częścią konkretnej definicji monady.

Powyższe jest takie samo, jak zdefiniowanie main = putStrLn "Hello, World!" , ponieważ wartość putStrLn "Hello, World!" ma już typ IO () . putStrLn "Hello, World!" jako „oświadczenie”, putStrLn "Hello, World!" może być postrzegany jako kompletny program, a Ty po prostu definiujesz main aby odnosić się do tego programu.

Możesz sprawdzić podpis putStrLn online :

putStrLn :: String -> IO ()
-- thus,
putStrLn (v :: String) :: IO ()

putStrLn to funkcja, która pobiera ciąg znaków jako argument i wysyła akcję We / Wy (tj. wartość reprezentującą program, który środowisko wykonawcze może wykonać). Środowisko wykonawcze zawsze wykonuje akcję o nazwie main , dlatego musimy po prostu zdefiniować ją jako równą putStrLn "Hello, World!" .

Factorial

Funkcja silnia to Haskell „Hello World!” (i ogólnie dla programowania funkcjonalnego) w tym sensie, że zwięźle pokazuje podstawowe zasady języka.

Wariacja 1

fac :: (Integral a) => a -> a
fac n = product [1..n]

Demo na żywo

  • Integral jest klasą liczb całkowitych. Przykłady obejmują Int i Integer .
  • (Integral a) => nakłada ograniczenie na typ a który należy do tej klasy
  • fac :: a -> a mówi, że fac jest funkcją, która przyjmuje a i zwraca a
  • product to funkcja, która gromadzi wszystkie liczby na liście, mnożąc je razem.
  • [1..n] to specjalna notacja, która zaprowadza się do enumFromTo 1 n , i jest zakresem liczb 1 ≤ x ≤ n .

Wariant 2

fac :: (Integral a) => a -> a
fac 0 = 1
fac n = n * fac (n - 1)

Demo na żywo

Ta odmiana wykorzystuje dopasowanie wzorca do podziału definicji funkcji na osobne przypadki. Pierwsza definicja jest wywoływana, jeśli argument ma wartość 0 (czasami nazywaną warunkiem zatrzymania), a druga definicja w przeciwnym razie (kolejność definicji jest znacząca). Jest to również przykład rekurencji, ponieważ fac odnosi się do siebie.


Warto zauważyć, że ze względu na reguły przepisywania obie wersje fac będą kompilować do identycznego kodu maszynowego przy korzystaniu z GHC z aktywowanymi optymalizacjami. Pod względem wydajności oba byłyby równoważne.

Fibonacciego, używając leniwej oceny

Leniwa ocena oznacza, że Haskell oceni tylko te elementy, których wartości są potrzebne.

Podstawowa definicja rekurencyjna to:

f (0)  <-  0
f (1)  <-  1
f (n)  <-  f (n-1) + f (n-2)

Jeśli zostanie oceniony bezpośrednio, będzie bardzo wolny. Ale wyobraź sobie, że mamy listę, która rejestruje wszystkie wyniki,

fibs !! n  <-  f (n) 

Następnie

                  ┌──────┐   ┌──────┐   ┌──────┐
                  │ f(0) │   │ f(1) │   │ f(2) │
fibs  ->  0 : 1 : │  +   │ : │  +   │ : │  +   │ :  .....
                  │ f(1) │   │ f(2) │   │ f(3) │
                  └──────┘   └──────┘   └──────┘

                  ┌────────────────────────────────────────┐
                  │ f(0)   :   f(1)   :   f(2)   :  .....  │ 
                  └────────────────────────────────────────┘
      ->  0 : 1 :               +
                  ┌────────────────────────────────────────┐
                  │ f(1)   :   f(2)   :   f(3)   :  .....  │
                  └────────────────────────────────────────┘

Jest to kodowane jako:

fibn n = fibs !! n
    where
    fibs = 0 : 1 : map f [2..]
    f n = fibs !! (n-1) + fibs !! (n-2)

Lub nawet jak

GHCi> let fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
GHCi> take 10 fibs
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

zipWith tworzy listę poprzez zastosowanie danej funkcji binarnej do odpowiednich elementów dwóch podanych jej list, więc zipWith (+) [x1, x2, ...] [y1, y2, ...] jest równe [x1 + y1, x2 + y2, ...] .

Innym sposobem pisania fibs jest z scanl funkcji :

GHCi> let fibs = 0 : scanl (+) 1 fibs
GHCi> take 10 fibs
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

scanl buduje listę częściowych wyników, które foldl , pracując od lewej do prawej wzdłuż listy danych wejściowych. Oznacza to, że scanl f z0 [x1, x2, ...] jest równe [z0, z1, z2, ...] where z1 = f z0 x1; z2 = f z1 x2; ...

Dzięki leniwej ocenie obie funkcje definiują nieskończone listy bez ich całkowitego obliczania. Oznacza to, że możemy napisać funkcję fib , pobierając n-ty element niezwiązanej sekwencji Fibonacciego:

GHCi> let fib n = fibs !! n  -- (!!) being the list subscript operator
-- or in point-free style:
GHCi> let fib = (fibs !!)
GHCi> fib 9
34

Pierwsze kroki

Online REPL

Najprostszym sposobem na rozpoczęcie pisania Haskell jest prawdopodobnie przejście na stronę internetową Haskell lub wypróbuj Haskell i skorzystaj z REPL online (read-eval-print-loop) na stronie głównej. Online REPL obsługuje większość podstawowych funkcji, a nawet niektóre operacje wejścia / wyjścia. Dostępny jest również podstawowy samouczek, który można uruchomić, wpisując polecenie help . Idealne narzędzie do rozpoczęcia nauki podstaw Haskell i wypróbowania kilku rzeczy.

GHC (i)

Dla programistów, którzy są gotowi zaangażować się nieco bardziej, istnieje GHCi , interaktywne środowisko dostarczane z kompilatorem Glorious / Glasgow Haskell . GHC można zainstalować osobno, ale jest to tylko kompilator. Aby móc zainstalować nowe biblioteki, należy również zainstalować narzędzia takie jak Cabal i Stack . Jeśli używasz systemu operacyjnego typu Unix, najłatwiejszą instalacją jest zainstalowanie stosu :

curl -sSL https://get.haskellstack.org/ | sh

Spowoduje to zainstalowanie GHC odizolowanego od reszty systemu, dzięki czemu można go łatwo usunąć. Wszystkie polecenia muszą być poprzedzone stack . Innym prostym podejściem jest instalacja platformy Haskell . Platforma występuje w dwóch wersjach:

  1. Minimalna dystrybucja zawiera tylko GHC (do kompilacji) i Cabal / Stack (do instalacji i kompilacji pakietów)
  2. Pełna dystrybucja dodatkowo zawiera narzędzia do opracowywania projektów, profilowania i analizy zasięgu. Dołączony jest także dodatkowy zestaw powszechnie używanych pakietów.

Platformy te można zainstalować, pobierając instalator i postępując zgodnie z instrukcjami lub za pomocą menedżera pakietów dystrybucji (pamiętaj, że nie gwarantuje się, że ta wersja będzie aktualna):

  • Ubuntu, Debian, Mint:

    sudo apt-get install haskell-platform
    
  • Fedora:

    sudo dnf install haskell-platform
    
  • Czerwony kapelusz:

    sudo yum install haskell-platform
    
  • Arch Linux:

    sudo pacman -S ghc cabal-install haskell-haddock-api \
                   haskell-haddock-library happy alex
    
  • Gentoo:

    sudo layman -a haskell
    sudo emerge haskell-platform
    
  • OSX z Homebrew:

    brew cask install haskell-platform
    
  • OSX z MacPorts:

    sudo port install haskell-platform
    

Po zainstalowaniu powinno być możliwe uruchomienie GHCi poprzez wywołanie polecenia ghci w dowolnym miejscu terminala. Jeśli instalacja przebiegła pomyślnie, konsola powinna wyglądać mniej więcej tak

me@notebook:~$ ghci
GHCi, version 6.12.1: http://www.haskell.org/ghc/  :? for help
Prelude> 

ewentualnie z dodatkowymi informacjami o bibliotekach, które zostały załadowane przed Prelude> . Teraz konsola stała się REPL Haskell i można wykonać kod Haskell jak w REPL online. Aby wyjść z tego interaktywnego środowiska, można wpisać :q lub :quit . Aby uzyskać więcej informacji na temat tego, co polecenia są dostępne w GHCi, typ :? jak wskazano na ekranie startowym.

Ponieważ pisanie tych samych rzeczy w jednym wierszu nie zawsze jest praktycznie takie, dobrym pomysłem może być zapisanie kodu Haskell w plikach. Te pliki zwykle mają rozszerzenie .hs dla rozszerzenia i można je załadować do REPL za pomocą :l lub :load .

Jak wspomniano wcześniej, GHCi jest częścią GHC , która w rzeczywistości jest kompilatorem. Tego kompilatora można użyć do przekształcenia pliku .hs z kodem Haskell w działający program. Ponieważ plik .hs może zawierać wiele funkcji, w pliku musi być zdefiniowana funkcja main . To będzie punkt wyjścia dla programu. Plik test.hs można skompilować za pomocą polecenia

ghc test.hs

spowoduje to utworzenie plików obiektowych i pliku wykonywalnego, jeśli nie wystąpią żadne błędy, a main funkcja zostanie poprawnie zdefiniowana.

Bardziej zaawansowane narzędzia

  1. Został już wspomniany wcześniej jako menedżer pakietów, ale stos może być użytecznym narzędziem do programowania Haskell na zupełnie inne sposoby. Po zainstalowaniu jest w stanie

    • instalacja (wiele wersji) GHC
    • tworzenie i rusztowanie projektów
    • zarządzanie zależnościami
    • projekty budowlane i testowe
    • analiza porównawcza
  2. IHaskell to jądro haskell dla IPython i pozwala łączyć (uruchamialny) kod z markdown i notacją matematyczną.

Liczby pierwsze

Kilka najistotniejszych wariantów:

Poniżej 100

import Data.List ( (\\) )

ps100 = ((([2..100] \\ [4,6..100]) \\ [6,9..100]) \\ [10,15..100]) \\ [14,21..100]

   -- = (((2:[3,5..100]) \\ [9,15..100]) \\ [25,35..100]) \\ [49,63..100]

   -- = (2:[3,5..100]) \\ ([9,15..100] ++ [25,35..100] ++ [49,63..100])

bez limitu

Sito Eratostenesa, używając pakietu danych-listy :

import qualified Data.List.Ordered

ps   = 2 : _Y ((3:) . minus [5,7..] . unionAll . map (\p -> [p*p, p*p+2*p..]))

_Y g = g (_Y g)   -- = g (g (_Y g)) = g (g (g (g (...)))) = g . g . g . g . ...

Tradycyjny

(nieoptymalne sito do podziału próby)

ps = sieve [2..]
     where
     sieve (x:xs) = [x] ++ sieve [y | y <- xs, rem y x > 0]

-- = map head ( iterate (\(x:xs) -> filter ((> 0).(`rem` x)) xs) [2..] )

Optymalny podział próby

ps = 2 : [n | n <- [3..], all ((> 0).rem n) $ takeWhile ((<= n).(^2)) ps]

-- = 2 : [n | n <- [3..], foldr (\p r-> p*p > n || (rem n p > 0 && r)) True ps]

Przejściowy

Od podziału próbnego do sita Eratostenesa:

[n | n <- [2..], []==[i | i <- [2..n-1], j <- [0,i..n], j==n]]

Najkrótszy kod

nubBy (((>1).).gcd) [2..]          -- i.e., nubBy (\a b -> gcd a b > 1) [2..]

nubBy jest również z Data.List , jak (\\) .

Deklarowanie wartości

Możemy zadeklarować serię wyrażeń w REPL w następujący sposób:

Prelude> let x = 5
Prelude> let y = 2 * 5 + x
Prelude> let result = y * 10
Prelude> x
5
Prelude> y
15
Prelude> result
150

Aby zadeklarować te same wartości w pliku, piszemy:

-- demo.hs

module Demo where
-- We declare the name of our module so 
-- it can be imported by name in a project.

x = 5

y = 2 * 5 + x

result = y * 10

Nazwy modułów są pisane wielkimi literami, w przeciwieństwie do nazw zmiennych.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow