Haskell Language Handledning
Komma igång med Haskell Language
Sök…
Anmärkningar
Haskell är ett avancerat rent funktionellt programmeringsspråk.
Funktioner:
- Statiskt skrivna: Varje uttryck i Haskell har en typ som bestäms vid sammanställningstiden. Statisk typkontroll är processen för att verifiera typsäkerheten för ett program baserat på analys av programmets text (källkod). Om ett program passerar en statisk typkontroll är det garanterat att programmet uppfyller vissa uppsättningar av säkerhetsegenskaper för alla möjliga ingångar.
- Rent funktionell : Varje funktion i Haskell är en funktion i matematisk mening. Det finns inga uttalanden eller instruktioner, endast uttryck som inte kan mutera variabler (lokala eller globala) eller åtkomsttillstånd som tid eller slumpmässiga nummer.
- Samtidig: Dess flaggskeppskompilator, GHC, kommer med en högpresterande parallellskrävsamlare och lättviktsbibliotek som innehåller ett antal användbara samtidiga primitiv och abstraktioner.
- Lat utvärdering: Funktioner utvärderar inte deras argument. Försenar utvärderingen av ett uttryck tills dess värde behövs.
- Allmänt: Haskell är byggd för att användas i alla sammanhang och miljöer.
- Paket: Open source-bidrag till Haskell är mycket aktivt med ett brett utbud av paket tillgängliga på de offentliga paketets servrar.
Den senaste standarden för Haskell är Haskell 2010. Från maj 2016 arbetar en grupp med nästa version, Haskell 2020.
Den officiella Haskell-dokumentationen är också en omfattande och användbar resurs. Bra ställe att hitta böcker, kurser, handböcker, manualer, guider etc.
versioner
Version | Utgivningsdatum |
---|---|
Haskell 2010 | 2012-07-10 |
Haskell 98 | 2002-12-01 |
Hej världen!
En grundläggande "Hej, världen!" program i Haskell kan uttryckas kortfattat i bara en eller två rader:
main :: IO ()
main = putStrLn "Hello, World!"
Den första raden är en annotation av valfri typ, som indikerar att main
är ett värde av typ IO ()
, som representerar en I / O-åtgärd som "beräknar" ett värde av typ ()
(läs "enhet"; den tomma tupeln som inte överför information) förutom att utföra några biverkningar på omvärlden (här, skriva ut en sträng på terminalen). Denna typanteckning utelämnas vanligtvis för main
eftersom den är den enda möjliga typen.
Lägg in den i en helloworld.hs
fil och kompilera den med en Haskell-kompilator, till exempel GHC:
ghc helloworld.hs
Exekvering av den sammanställda filen kommer att resultera i utskriften "Hello, World!"
skrivs ut på skärmen:
./helloworld
Hello, World!
Alternativt gör runhaskell
eller runghc
det möjligt att köra programmet i tolkat läge utan att behöva kompilera det:
runhaskell helloworld.hs
Den interaktiva REPL kan också användas istället för att sammanställa. Den levereras med de flesta Haskell-miljöer, till exempel ghci
som levereras med GHC-kompilatorn:
ghci> putStrLn "Hello World!"
Hello, World!
ghci>
Ladda alternativt skript till ghci från en fil med load
(eller :l
):
ghci> :load helloworld
:reload
(eller :r
) laddar om allt i 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.
Förklaring:
Den här första raden är en typsignatur som förklarar typen av main
:
main :: IO ()
Värden av typ IO ()
beskriver handlingar som kan interagera med omvärlden.
Eftersom Haskell har ett fullständigt system av typen Hindley-Milner som möjliggör automatisk inferens av typ, är typsignaturer tekniskt frivilliga: om du helt enkelt utelämnar main :: IO ()
kommer kompilatorn att kunna dra slutsatsen på egen hand av analysera definitionen av main
. Det anses emellertid mycket som dålig stil att inte skriva typsignaturer för definitioner på toppnivå. Skälen inkluderar:
Typsignaturer i Haskell är en mycket användbar dokumentation eftersom typsystemet är så uttrycksfullt att du ofta kan se vilken typ av funktion en funktion är bra för genom att bara titta på dess typ. Denna "dokumentation" kan enkelt nås med verktyg som GHCi. Och till skillnad från normal dokumentation kommer kompilatorens typkontroll att se till att den faktiskt matchar funktionsdefinitionen!
Skriv signaturer håller buggar lokala . Om du gör ett misstag i en definition utan att tillhandahålla dess typsignatur, kanske kompilatorn inte omedelbart rapporterar ett fel utan istället helt enkelt sluta sig till en nonsensisk typ för den, med vilken den faktiskt typcheckar. Du kan då få ett kryptiskt felmeddelande när du använder det värdet. Med en signatur är kompilatorn mycket bra på att upptäcka buggar precis där de händer.
Denna andra raden gör själva arbetet:
main = putStrLn "Hello, World!"
Om du kommer från ett imperativspråk kan det vara bra att notera att denna definition också kan skrivas som:
main = do {
putStrLn "Hello, World!" ;
return ()
}
Eller likvärdigt (Haskell har layoutbaserad parsing; men se upp för att blanda flikar och mellanslag inkonsekvent vilket förvirrar denna mekanism):
main = do
putStrLn "Hello, World!"
return ()
Varje rad i ett do
block representerar någon monadisk (här, I / O) -beräkning , så att hela do
blocket representerar den totala handlingen som består av dessa delsteg genom att kombinera dem på ett sätt specifikt för den givna monaden (för I / O detta betyder att bara köra dem efter varandra).
do
syntaxen är i sig själv ett syntaktiskt socker för monader, som IO
här, och return
är en no-op-åtgärd som ger sitt argument utan att utföra några biverkningar eller ytterligare beräkningar som kan vara en del av en viss monaddefinition.
Ovanstående är samma sak som att definiera main = putStrLn "Hello, World!"
, eftersom värdet putStrLn "Hello, World!"
har redan typen IO ()
. Betraktas som ett "uttalande", putStrLn "Hello, World!"
kan ses som ett komplett program, och du definierar helt enkelt main
att hänvisa till det här programmet.
Du kan leta upp signaturen på putStrLn
online :
putStrLn :: String -> IO ()
-- thus,
putStrLn (v :: String) :: IO ()
putStrLn
är en funktion som tar en sträng som sitt argument och matar ut en I / O-åtgärd (dvs. ett värde som representerar ett program som runtime kan utföra). Runtiden utför alltid åtgärden som heter main
, så vi behöver helt enkelt definiera den som lika med putStrLn "Hello, World!"
.
Fakultet
Fabriksfunktionen är en Haskell "Hello World!" (och för funktionell programmering i allmänhet) i den meningen att det kortfattat visar grundprinciperna för språket.
Variation 1
fac :: (Integral a) => a -> a
fac n = product [1..n]
-
Integral
är klassen av integrerade taltyper. Exempel inkluderarInt
ochInteger
. -
(Integral a) =>
sätter en begränsning för typena
ska vara i nämnda klass -
fac :: a -> a
säger attfac
är en funktion som tar ena
och returnerar ena
-
product
är en funktion som samlar alla siffror i en lista genom att multiplicera dem tillsammans. -
[1..n]
är en särskild notation som desugar tillenumFromTo 1 n
, och är intervallet med siffrorna1 ≤ x ≤ n
.
Variation 2
fac :: (Integral a) => a -> a
fac 0 = 1
fac n = n * fac (n - 1)
Denna variation använder mönstermatchning för att dela funktionsdefinitionen i separata fall. Den första definitionen åberopas om argumentet är 0
(ibland kallat stoppvillkor) och den andra definitionen annars (definitionen är betydelsefull). Det exemplifierar också rekursion eftersom fac
hänvisar till sig själv.
Det är värt att notera att på grund av omskrivningsregler kommer båda versionerna av fac
att kompilera till identisk maskinkod när GHC används med optimeringar aktiverade. Så när det gäller effektivitet skulle de två vara likvärdiga.
Fibonacci, använder lat utvärdering
Lat utvärdering innebär att Haskell endast utvärderar listobjekt vars värden behövs.
Den grundläggande rekursiva definitionen är:
f (0) <- 0
f (1) <- 1
f (n) <- f (n-1) + f (n-2)
Om det utvärderas direkt kommer det att gå mycket långsamt. Men föreställ dig att vi har en lista som registrerar alla resultat,
fibs !! n <- f (n)
Sedan
┌──────┐ ┌──────┐ ┌──────┐
│ 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) : ..... │
└────────────────────────────────────────┘
Detta kodas som:
fibn n = fibs !! n
where
fibs = 0 : 1 : map f [2..]
f n = fibs !! (n-1) + fibs !! (n-2)
Eller till och med som
GHCi> let fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
GHCi> take 10 fibs
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
zipWith
gör en lista genom att tillämpa en given binär funktion på motsvarande element i de två listorna som ges till den, så zipWith (+) [x1, x2, ...] [y1, y2, ...]
är lika med [x1 + y1, x2 + y2, ...]
.
Ett annat sätt att skriva fibs
är med scanl
funktionen :
GHCi> let fibs = 0 : scanl (+) 1 fibs
GHCi> take 10 fibs
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
scanl
bygger listan över foldl
som foldl
skulle ge, och arbetar från vänster till höger längs inmatningslistan. Det vill säga, scanl f z0 [x1, x2, ...]
är lika med [z0, z1, z2, ...] where z1 = f z0 x1; z2 = f z1 x2; ...
Tack vare lat utvärdering definierar båda funktionerna oändliga listor utan att beräkna dem helt. Det vill säga, vi kan skriva en fib
funktion och hämta det nionde elementet i den obegränsade Fibonacci-sekvensen:
GHCi> let fib n = fibs !! n -- (!!) being the list subscript operator
-- or in point-free style:
GHCi> let fib = (fibs !!)
GHCi> fib 9
34
Komma igång
Online REPL
Det enklaste sättet att komma igång med att skriva Haskell är förmodligen genom att gå till Haskell-webbplatsen eller Prova Haskell och använda online-REPL (read-eval-print-loop) på hemsidan. Online REPL stöder de flesta grundläggande funktioner och till och med en del IO. Det finns också en grundläggande handledning tillgängliga som kan startas genom att skriva kommandot help
. Ett idealiskt verktyg för att börja lära sig grunderna i Haskell och prova några saker.
GHC (i)
För programmerare som är redo att engagera sig lite mer finns det GHCi , en interaktiv miljö som kommer med Glorious / Glasgow Haskell Compiler . GHC kan installeras separat, men det är bara en kompilator. För att kunna installera nya bibliotek måste också verktyg som Cabal och Stack installeras. Om du kör ett Unix-liknande operativsystem är den enklaste installationen att installera Stack med:
curl -sSL https://get.haskellstack.org/ | sh
Detta installerar GHC isolerat från resten av ditt system, så det är lätt att ta bort. Alla kommandon måste dock föregås av stack
. En annan enkel metod är att installera en Haskell-plattform . Plattformen finns i två smaker:
- Den minimala distributionen innehåller endast GHC (att sammanställa) och Cabal / Stack (för att installera och bygga paket)
- Den fullständiga distributionen innehåller dessutom verktyg för projektutveckling, profilering och täckningsanalys. Dessutom ingår en extra uppsättning av ofta använda paket.
Dessa plattformar kan installeras genom att ladda ner ett installationsprogram och följa instruktionerna eller genom att använda din distributionspakethanterare (Observera att denna version inte garanteras vara uppdaterad):
Ubuntu, Debian, Mint:
sudo apt-get install haskell-platform
Fedora:
sudo dnf install haskell-platform
Röd hatt:
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 med Homebrew:
brew cask install haskell-platform
OSX med MacPorts:
sudo port install haskell-platform
När det väl är installerat bör det vara möjligt att starta GHCi genom att påkalla ghci
kommandot var som helst i terminalen. Om installationen gick bra, bör konsolen se ut något
me@notebook:~$ ghci
GHCi, version 6.12.1: http://www.haskell.org/ghc/ :? for help
Prelude>
eventuellt med lite mer information om vilka bibliotek som har laddats före Prelude>
. Nu har konsolen blivit en Haskell REPL och du kan köra Haskell-kod som med online-REPL. För att avsluta den interaktiva miljön kan man skriva :q
eller :quit
. För mer information om vilka kommandon som finns tillgängliga i GHCi , skriv :?
som anges i startskärmen.
Eftersom att skriva samma saker om och om igen på en enda rad inte alltid är så praktiskt, kan det vara en bra idé att skriva Haskell-koden i filer. Dessa filer har normalt .hs
för en anknytning och kan laddas i REPL med :l
eller :load
.
Som nämnts tidigare är GHCi en del av GHC , som faktiskt är en kompilator. Denna kompilator kan användas för att omvandla en .hs
fil med Haskell-kod till ett löpande program. Eftersom en .hs
fil kan innehålla en hel del funktioner, en main
måste funktionen definieras i filen. Detta kommer att vara startpunkten för programmet. Filen test.hs
kan kompileras med kommandot
ghc test.hs
detta kommer att skapa objektfiler och en körbar om det inte fanns några fel och main
definierades korrekt.
Mer avancerade verktyg
Det har redan nämnts tidigare som paketansvarig, men stack kan vara ett användbart verktyg för Haskell-utveckling på helt olika sätt. När den är installerad kan den
- installera (flera versioner av) GHC
- projekt skapande och byggnadsställningar
- beroendehantering
- byggande och testning av projekt
- benchmark
IHaskell är en haskellkärna för IPython och gör det möjligt att kombinera (körbar) kod med markdown och matematisk notation.
Primes
Några framstående varianter:
Under 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])
Obegränsat
Sikt av Eratosthenes med datapaketlista :
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 . ...
Traditionell
(en suboptimal försöksdelningssikt)
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..] )
Optimal provavdelning
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]
övergångs~~POS=TRUNC
Från försöksdelning till sikt av Eratosthenes:
[n | n <- [2..], []==[i | i <- [2..n-1], j <- [0,i..n], j==n]]
Den kortaste koden
nubBy (((>1).).gcd) [2..] -- i.e., nubBy (\a b -> gcd a b > 1) [2..]
nubBy
kommer också från Data.List
, som (\\)
.
Förklarande värden
Vi kan förklara en serie uttryck i REPL så här:
Prelude> let x = 5
Prelude> let y = 2 * 5 + x
Prelude> let result = y * 10
Prelude> x
5
Prelude> y
15
Prelude> result
150
För att deklarera samma värden i en fil skriver vi följande:
-- 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
Modulnamn har stora bokstäver, till skillnad från variabla namn.