Sök…


Anmärkningar

Haskell logotyp

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]

Live-demo

  • Integral är klassen av integrerade taltyper. Exempel inkluderar Int och Integer .
  • (Integral a) => sätter en begränsning för typen a ska vara i nämnda klass
  • fac :: a -> a säger att fac är en funktion som tar en a och returnerar en a
  • 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 till enumFromTo 1 n , och är intervallet med siffrorna 1 ≤ x ≤ n .

Variation 2

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

Live-demo

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:

  1. Den minimala distributionen innehåller endast GHC (att sammanställa) och Cabal / Stack (för att installera och bygga paket)
  2. 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

  1. 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
  2. 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.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow