Haskell LanguageErste Schritte mit Haskell Language

Bemerkungen

Haskell-Logo

Haskell ist eine fortschrittliche rein funktionale Programmiersprache.

Eigenschaften:

  • Statisch geschrieben: Jeder Ausdruck in Haskell hat einen Typ, der zur Kompilierzeit bestimmt wird. Bei der statischen Typprüfung wird die Typsicherheit eines Programms basierend auf der Analyse des Programmtexts (Quellcode) überprüft. Wenn ein Programm einen statischen Typprüfer durchläuft, erfüllt das Programm garantiert einige Sicherheitseigenschaften für alle möglichen Eingaben.
  • Rein funktionell : Jede Funktion in Haskell ist eine Funktion im mathematischen Sinne. Es gibt keine Anweisungen oder Anweisungen, nur Ausdrücke, die keine Variablen (lokal oder global) verändern können und auf Status wie Zeit- oder Zufallszahlen nicht zugreifen können.
  • Concurrent: Der Flaggschiff-Compiler GHC verfügt über eine leistungsstarke parallele Garbage Collection und eine leichte Parallelitätsbibliothek, die eine Reihe nützlicher Parallelitätsgrundelemente und Abstraktionen enthält.
  • Faule Auswertung: Funktionen bewerten ihre Argumente nicht. Verzögert die Auswertung eines Ausdrucks, bis sein Wert benötigt wird.
  • Allgemein: Haskell ist für die Verwendung in allen Kontexten und Umgebungen konzipiert.
  • Pakete: Der Beitrag von Open Source zu Haskell ist sehr aktiv und bietet eine breite Palette von Paketen, die auf den öffentlichen Paketservern verfügbar sind.

Der neueste Standard von Haskell ist Haskell 2010. Ab Mai 2016 arbeitet eine Gruppe an der nächsten Version, Haskell 2020.

Die offizielle Dokumentation von Haskell ist auch eine umfassende und nützliche Informationsquelle. Toller Ort, um Bücher, Kurse, Tutorials, Handbücher, Anleitungen usw. zu finden.

Versionen

Ausführung Veröffentlichungsdatum
Haskell 2010 2012-07-10
Haskell 98 2002-12-01

Hallo Welt!

Ein einfaches "Hallo, Welt!" Das Programm in Haskell kann in einer oder zwei Zeilen präzise ausgedrückt werden:

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

Die erste Zeile ist eine optionale Typanmerkung, die angibt, dass main ein Wert vom Typ IO () , der eine E / A-Aktion darstellt, die einen Wert von type () "berechnet" (lesen "unit"; das leere Tupel, das keine Informationen übermittelt). Neben der Durchführung von Nebeneffekten auf der Außenwelt (Drucken einer Zeichenfolge am Terminal). Diese Typanmerkung wird für main normalerweise weggelassen, da dies der einzig mögliche Typ ist.

helloworld.hs Sie dies in eine helloworld.hs Datei ein und kompilieren Sie es mit einem Haskell-Compiler wie GHC:

ghc helloworld.hs

Die Ausführung der kompilierten Datei führt zur Ausgabe "Hello, World!" auf dem Bildschirm gedruckt werden:

./helloworld
Hello, World!

Alternativ können Sie mit runhaskell oder runghc das Programm im interpretierten Modus runghc , ohne es übersetzen zu müssen:

runhaskell helloworld.hs

Die interaktive REPL kann auch anstelle des Kompilierens verwendet werden. Es wird mit den meisten Haskell-Umgebungen ghci , z. B. ghci das mit dem GHC-Compiler ghci :

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

Alternativ können Sie Skripte mit Hilfe von load (oder :l ) aus einer Datei in ghci load :

ghci> :load helloworld

:reload (oder :r ) lädt alles in 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.

Erläuterung:

Diese erste Zeile ist eine Typensignatur, die den main Typ angibt:

main :: IO ()

Werte des Typs IO () beschreiben Aktionen, die mit der Außenwelt interagieren können.

Da Haskell über ein voll entwickeltes Hindley-Milner-Typsystem verfügt, das eine automatische Typinferenz zulässt, sind Typunterschriften technisch optional: Wenn Sie einfach main :: IO () weglassen, kann der Compiler den Typ selbst ermitteln Analyse der Definition von main . Es wird jedoch als ungünstig angesehen, keine Typunterschriften für Definitionen der obersten Ebene zu schreiben. Gründe dafür sind:

  • Typunterschriften in Haskell sind eine sehr hilfreiche Dokumentation, da das Typsystem so aussagekräftig ist, dass Sie oft sehen können, für welche Art von Funktionen eine Funktion einfach durch Betrachten des Typs geeignet ist. Diese "Dokumentation" kann bequem mit Tools wie GHCi aufgerufen werden. Im Gegensatz zur normalen Dokumentation stellt der Type Checker des Compilers sicher, dass er tatsächlich mit der Funktionsdefinition übereinstimmt!

  • Typ Signaturen halten Fehler lokal . Wenn Sie in einer Definition einen Fehler machen, ohne die Typensignatur anzugeben, meldet der Compiler möglicherweise nicht sofort einen Fehler, sondern leitet einfach einen unsinnigen Typ für die Definition ab, mit dem er tatsächlich eine Typüberprüfung vornimmt. Wenn Sie diesen Wert verwenden , wird möglicherweise eine kryptische Fehlermeldung angezeigt. Mit einer Signatur ist der Compiler sehr gut darin, Fehler dort zu erkennen, wo sie auftreten.

Diese zweite Zeile erledigt die eigentliche Arbeit:

main = putStrLn "Hello, World!"

Wenn Sie aus einer imperativen Sprache stammen, kann es hilfreich sein zu beachten, dass diese Definition auch wie folgt geschrieben werden kann:

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

Oder gleichwertig (Haskell hat ein auf Layout basierendes Parsing; achten Sie jedoch darauf, Tabs und Leerzeichen inkonsistent zu mischen, was diesen Mechanismus verwirren wird):

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

Jede Zeile in einem do Block repräsentiert eine monadische (hier E / A-) Berechnung , so dass der ganze do Block die gesamte Aktion darstellt, die aus diesen Unterschritten besteht, indem sie auf eine für die jeweilige Monade spezifische Weise kombiniert werden (für E / A Dies bedeutet, dass sie einfach nacheinander ausgeführt werden.

Die do Syntax ist selbst ein syntaktischer Zucker für Monaden wie IO hier, und return ist eine No-Op-Aktion, die ihr Argument erzeugt, ohne Nebeneffekte oder zusätzliche Berechnungen auszuführen, die Teil einer bestimmten Monadendefinition sein könnten.

Das obige ist dasselbe wie das Definieren von main = putStrLn "Hello, World!" , weil der Wert putStrLn "Hello, World!" hat bereits den Typ IO () . Als "Aussage" betrachtet, putStrLn "Hello, World!" kann als komplettes Programm betrachtet werden, und Sie definieren einfach main , um auf dieses Programm zu verweisen.

Die Signatur von putStrLn online nachschlagen :

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

putStrLn ist eine Funktion, die einen String als Argument verwendet und eine E / A-Aktion ausgibt (dh einen Wert, der ein Programm darstellt, das die Laufzeitumgebung ausführen kann). Die Laufzeitumgebung führt immer die Aktion mit dem Namen main , also müssen wir sie nur als putStrLn "Hello, World!" Definieren. .

Fakultät

Die faktorielle Funktion ist ein Haskell "Hello World!" (und für die funktionale Programmierung allgemein) in dem Sinne, dass sie die grundlegenden Prinzipien der Sprache auf einfache Weise demonstriert.

Variante 1

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

Live-Demo

  • Integral ist die Klasse von Integralnummerntypen. Beispiele sind Int und Integer .
  • (Integral a) => setzt eine Einschränkung auf den Typ a , der in dieser Klasse enthalten sein soll
  • fac :: a -> a sagt , dass fac eine Funktion, die eine nimmt a und gibt einen a
  • product ist eine Funktion, die alle Zahlen in einer Liste durch Multiplikation zusammenfasst.
  • [1..n] ist eine spezielle Notation, die zu enumFromTo 1 n , und ist der Zahlenbereich 1 ≤ x ≤ n .

Variante 2

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

Live-Demo

Diese Variante verwendet Mustervergleich, um die Funktionsdefinition in separate Fälle aufzuteilen. Die erste Definition wird aufgerufen, wenn das Argument 0 (manchmal als Stoppbedingung bezeichnet) und ansonsten die zweite Definition (die Reihenfolge der Definitionen ist signifikant). Es veranschaulicht auch die Rekursion, da sich fac auf sich selbst bezieht.


Es ist erwähnenswert, dass beide Versionen von fac aufgrund von Rewrite-Regeln zu identischem Maschinencode kompilieren, wenn GHC mit aktivierten Optimierungen verwendet wird. In Bezug auf die Effizienz wären die beiden äquivalent.

Fibonacci, mit fauler Bewertung

Faule Auswertung bedeutet, dass Haskell nur Listenelemente auswertet, deren Werte benötigt werden.

Die grundlegende rekursive Definition lautet:

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

Bei direkter Auswertung wird es sehr langsam sein. Aber stellen Sie sich vor, wir haben eine Liste, die alle Ergebnisse aufzeichnet.

fibs !! n  <-  f (n) 

Dann

                  ┌──────┐   ┌──────┐   ┌──────┐
                  │ 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)   :  .....  │
                  └────────────────────────────────────────┘

Dies ist wie folgt codiert:

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

Oder sogar als

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

zipWith eine Liste durch Anwenden einer gegebenen zipWith auf entsprechende Elemente der beiden Listen, sodass zipWith (+) [x1, x2, ...] [y1, y2, ...] gleich [x1 + y1, x2 + y2, ...] .

Eine andere Möglichkeit, fibs schreiben, ist die Funktion scanl :

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

scanl die Liste der Teilergebnisse, die foldl erzeugen würde, wobei es von links nach rechts entlang der Eingabeliste arbeitet. Das heißt, scanl f z0 [x1, x2, ...] ist gleich [z0, z1, z2, ...] where z1 = f z0 x1; z2 = f z1 x2; ...

Dank der faulen Auswertung definieren beide Funktionen unendliche Listen, ohne sie vollständig zu berechnen. Das heißt, wir können eine fib Funktion schreiben und das n-te Element der unbegrenzten Fibonacci-Sequenz abrufen:

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

Fertig machen

Online REPL

Der einfachste Weg, um mit dem Schreiben von Haskell zu beginnen, ist wahrscheinlich die Haskell-Website oder Try Haskell und die Online-REPL (Read-Eval-Print-Loop) auf der Startseite. Die Online-REPL unterstützt die meisten grundlegenden Funktionen und sogar einige E / A. Es gibt auch ein grundlegendes Tutorial zur Verfügung , die durch die Eingabe des Befehls gestartet werden kann help . Ein ideales Werkzeug, um die Grundlagen von Haskell zu erlernen und einige Dinge auszuprobieren.

GHC (i)

Für Programmierer, die bereit sind, sich ein wenig mehr zu engagieren, gibt es GHCi , eine interaktive Umgebung, die mit dem Glorious / Glasgow Haskell Compiler geliefert wird. Der GHC kann separat installiert werden, dies ist jedoch nur ein Compiler. Um neue Bibliotheken installieren zu können, müssen auch Tools wie Cabal und Stack installiert werden. Wenn Sie ein Unix-ähnliches Betriebssystem verwenden, ist die einfachste Installation die Installation von Stack mit:

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

Dadurch wird GHC isoliert vom Rest Ihres Systems installiert, sodass es leicht entfernt werden kann. Allen Befehlen muss jedoch ein stack vorangestellt werden. Ein anderer einfacher Ansatz ist die Installation einer Haskell-Plattform . Die Plattform gibt es in zwei Ausführungen:

  1. Die minimale Distribution enthält nur GHC (zum Kompilieren) und Cabal / Stack (zum Installieren und Erstellen von Paketen).
  2. Die Volldistribution enthält zusätzlich Werkzeuge zur Projektentwicklung, Profilerstellung und Abdeckungsanalyse. Außerdem ist ein zusätzlicher Satz weit verbreiteter Pakete enthalten.

Diese Plattformen können installiert werden, indem Sie ein Installationsprogramm herunterladen und den Anweisungen folgen oder den Paketmanager Ihrer Distribution verwenden (beachten Sie, dass diese Version nicht garantiert auf dem neuesten Stand ist):

  • Ubuntu, Debian, Minze:

    sudo apt-get install haskell-platform
    
  • Fedora:

    sudo dnf install haskell-platform
    
  • Roter Hut:

    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 mit Homebrew:

    brew cask install haskell-platform
    
  • OSX mit MacPorts:

    sudo port install haskell-platform
    

Nach der Installation sollte es möglich sein, GHCi durch Aufrufen des ghci beliebigen Stelle im Terminal zu starten. Wenn die Installation erfolgreich verlief, sollte die Konsole ungefähr so ​​aussehen

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

möglicherweise mit einigen weiteren Informationen darüber, welche Bibliotheken vor dem Prelude> geladen wurden Prelude> . Nun ist aus der Konsole eine Haskell-REPL geworden, und Sie können Haskell-Code wie bei der Online-REPL ausführen. Um diese interaktive Umgebung zu beenden, können Sie Folgendes :q oder :quit . Weitere Informationen darüber , welche Befehle sind in GHCi, Typ :? wie im Startbildschirm angegeben.

Da es immer nicht immer praktisch ist, dieselben Dinge immer wieder in einer einzigen Zeile zu schreiben, ist es möglicherweise eine gute Idee, den Haskell-Code in Dateien zu schreiben. Diese Dateien haben normalerweise .hs für eine Erweiterung und können mithilfe von :l oder :load in die REPL geladen :load .

Wie bereits erwähnt, ist GHCi Teil des GHC , der eigentlich ein Compiler ist. Dieser Compiler kann verwendet werden, um eine .hs Datei mit Haskell-Code in ein laufendes Programm .hs . Da eine .hs Datei viele Funktionen enthalten kann, muss in der Datei eine main definiert werden. Dies ist der Ausgangspunkt für das Programm. Die Datei test.hs kann mit dem Befehl kompiliert werden

ghc test.hs

Dadurch werden Objektdateien und eine ausführbare Datei erstellt, wenn keine Fehler aufgetreten sind und die main korrekt definiert wurde.

Fortgeschrittenere Tools

  1. Es wurde bereits zuvor als Paketmanager erwähnt, aber Stack kann auf ganz unterschiedliche Weise ein nützliches Werkzeug für die Entwicklung von Haskell sein. Einmal installiert, ist es in der Lage

    • (mehrere Versionen von) GHC installieren
    • Projekterstellung und Gerüstbau
    • Abhängigkeitsmanagement
    • Bau- und Testprojekte
    • Benchmarking
  2. IHaskell ist ein Hashkell-Kernel für IPython und ermöglicht die Kombination von (lauffähigem) Code mit Markdown und mathematischer Notation.

Primes

Einige hervorstechende Varianten:

Unter 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])

Unbegrenzt

Sieat von Eratosthenes mit dem Paket data-ordlist :

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

(ein suboptimales Probeabteilungssieb)

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..] )

Optimale Probeabteilung

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]

Übergang

Von der Probeabteilung bis zum Sieb des Eratosthenes:

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

Der kürzeste Code

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

nubBy ist auch aus Data.List , wie (\\) .

Werte deklarieren

Wir können eine Reihe von Ausdrücken in der REPL wie folgt deklarieren:

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

Um die gleichen Werte in einer Datei zu deklarieren, schreiben wir Folgendes:

-- 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

Modulnamen werden im Gegensatz zu Variablennamen großgeschrieben.