Haskell Language Tutorial
Iniziare con Haskell Language
Ricerca…
Osservazioni
Haskell è un linguaggio di programmazione puramente funzionale avanzato.
Caratteristiche:
- Caratterizzato staticamente: ogni espressione in Haskell ha un tipo determinato al momento della compilazione. Il controllo statico del tipo è il processo di verifica della sicurezza del tipo di un programma basato sull'analisi del testo di un programma (codice sorgente). Se un programma supera un controllo di tipo statico, allora il programma è garantito per soddisfare alcune serie di proprietà di sicurezza del tipo per tutti i possibili input.
- Puramente funzionale : ogni funzione in Haskell è una funzione in senso matematico. Non ci sono istruzioni o istruzioni, solo espressioni che non possono mutare variabili (locali o globali) né accedere allo stato come il tempo o numeri casuali.
- Concorrente: il suo compilatore di punta, GHC, è dotato di un garbage collector parallelo ad alte prestazioni e di una libreria leggera di concomitanza contenente un numero di primitive e astrazioni di concorrenza utili.
- Valutazione pigra: le funzioni non valutano i loro argomenti. Ritarda la valutazione di un'espressione finché non è necessario il suo valore.
- Scopo generale: Haskell è stato progettato per essere utilizzato in tutti i contesti e ambienti.
- Pacchetti: il contributo open source a Haskell è molto attivo con una vasta gamma di pacchetti disponibili sui server dei pacchetti pubblici.
L'ultimo standard di Haskell è Haskell 2010. A maggio 2016, un gruppo sta lavorando alla prossima versione, Haskell 2020.
La documentazione ufficiale di Haskell è anche una risorsa completa e utile. Ottimo posto per trovare libri, corsi, tutorial, manuali, guide, ecc.
Versioni
Versione | Data di rilascio |
---|---|
Haskell 2010 | 2012-07-10 |
Haskell 98 | 2002/12/01 |
Ciao mondo!
Un semplice "Hello, World!" il programma in Haskell può essere espresso in modo conciso in una o due righe:
main :: IO ()
main = putStrLn "Hello, World!"
La prima riga è un'annotazione di tipo opzionale, che indica che main
è un valore di tipo IO ()
, che rappresenta un'azione di I / O che "calcola" un valore di tipo ()
(leggi "unità"; la tupla vuota che non trasmette informazioni) oltre ad eseguire alcuni effetti collaterali sul mondo esterno (qui, stampando una stringa al terminale). Questa annotazione di tipo viene generalmente omessa per main
perché è il suo solo tipo possibile.
Metti questo in un file helloworld.hs
e compila usando un compilatore Haskell, come GHC:
ghc helloworld.hs
L'esecuzione del file compilato produrrà l'output "Hello, World!"
in fase di stampa sullo schermo:
./helloworld
Hello, World!
In alternativa, runhaskell
o runghc
rendono possibile eseguire il programma in modalità interpretata senza doverlo compilare:
runhaskell helloworld.hs
Il REPL interattivo può anche essere usato al posto della compilazione. Viene fornito con la maggior parte degli ambienti Haskell, come ghci
che viene fornito con il compilatore GHC:
ghci> putStrLn "Hello World!"
Hello, World!
ghci>
In alternativa, caricare gli script in ghci da un file usando load
(o :l
):
ghci> :load helloworld
:reload
(o :r
) ricarica tutto 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.
Spiegazione:
Questa prima riga è una firma di tipo, che dichiara il tipo di main
:
main :: IO ()
I valori di tipo IO ()
descrivono le azioni che possono interagire con il mondo esterno.
Poiché Haskell ha un sistema di tipo Hindley-Milner a tutti gli effetti che consente l'inferenza automatica del tipo, le firme dei tipi sono tecnicamente facoltative: se si omette semplicemente il main :: IO ()
, il compilatore sarà in grado di dedurre il tipo da solo analizzando la definizione di main
. Tuttavia, è molto considerato uno stile negativo non scrivere le firme dei tipi per le definizioni di livello superiore. I motivi includono:
Digitare le firme in Haskell è una documentazione molto utile perché il sistema dei tipi è così espressivo che spesso si può vedere che tipo di cosa è utile per una funzione semplicemente osservando il suo tipo. Questa "documentazione" può essere facilmente accessibile con strumenti come GHCi. E a differenza della normale documentazione, il controllo del tipo del compilatore farà in modo che corrisponda effettivamente alla definizione della funzione!
Le firme dei tipi mantengono i bug locali . Se si commette un errore in una definizione senza fornire la propria firma del tipo, il compilatore potrebbe non segnalare immediatamente un errore, ma invece dedurre semplicemente un tipo non sensato per esso, con il quale in realtà digita i caratteri. È quindi possibile ottenere un messaggio di errore criptico quando si utilizza tale valore. Con una firma, il compilatore è molto bravo a individuare gli errori nel punto in cui accadono.
Questa seconda riga fa il lavoro reale:
main = putStrLn "Hello, World!"
Se vieni da una lingua imperativa, potrebbe essere utile notare che questa definizione può anche essere scritta come:
main = do {
putStrLn "Hello, World!" ;
return ()
}
O in modo equivalente (Haskell ha un parsing basato sul layout, ma attenzione a mescolare in modo incoerente tab e spazi che confonderanno questo meccanismo):
main = do
putStrLn "Hello, World!"
return ()
Ogni riga in un blocco do
rappresenta un calcolo monodico (qui, I / O), in modo che l'intero blocco do
rappresenti l'azione complessiva composta da questi sottomasi combinandoli in un modo specifico per la monade data (per I / O questo significa solo eseguirli uno dopo l'altro).
La sintassi do
è a sua volta uno zucchero sintattico per le monadi, come IO
qui, e return
è un'azione no-op che produce il suo argomento senza eseguire effetti collaterali o calcoli aggiuntivi che potrebbero far parte di una particolare definizione di monade.
Quanto sopra è lo stesso della definizione di main = putStrLn "Hello, World!"
, perché il valore putStrLn "Hello, World!"
ha già il tipo IO ()
. Visto come una "dichiarazione", putStrLn "Hello, World!"
può essere visto come un programma completo, e basta semplicemente definire main
per riferirsi a questo programma.
Puoi cercare la firma di putStrLn
online :
putStrLn :: String -> IO ()
-- thus,
putStrLn (v :: String) :: IO ()
putStrLn
è una funzione che accetta una stringa come argomento e genera un'azione di I / O (ovvero un valore che rappresenta un programma che il runtime può eseguire). Il runtime esegue sempre l'azione denominata main
, quindi è sufficiente definirla come uguale a putStrLn "Hello, World!"
.
Fattoriale
La funzione fattoriale è un "Hello World!" Di Haskell (e per la programmazione funzionale in generale), nel senso che dimostra succintamente i principi di base della lingua.
Variazione 1
fac :: (Integral a) => a -> a
fac n = product [1..n]
-
Integral
è la classe dei tipi di numeri interi. Gli esempi includonoInt
eInteger
. -
(Integral a) =>
inserisce un vincolo sul tipoa
per essere in detta classe -
fac :: a -> a
dice chefac
è una funzione che accetta una
e restituisce una
-
product
è una funzione che accumula tutti i numeri in una lista moltiplicandoli insieme. -
[1..n]
è una notazione speciale che desugars toenumFromTo 1 n
, ed è l'intervallo di numeri1 ≤ x ≤ n
.
Variazione 2
fac :: (Integral a) => a -> a
fac 0 = 1
fac n = n * fac (n - 1)
Questa variante utilizza la corrispondenza del modello per suddividere la definizione della funzione in casi separati. La prima definizione viene invocata se l'argomento è 0
(a volte chiamato la condizione di arresto) e la seconda definizione in caso contrario (l'ordine delle definizioni è significativo). Inoltre esemplifica la ricorsione come fac
fa riferimento a se stessa.
Vale la pena notare che, a causa delle regole di riscrittura, entrambe le versioni di fac
verranno compilate allo stesso codice macchina quando si utilizza GHC con le ottimizzazioni attivate. Quindi, in termini di efficienza, i due sarebbero equivalenti.
Fibonacci, usando la valutazione pigra
Valutazione pigra significa che Haskell valuterà solo gli elementi della lista i cui valori sono necessari.
La definizione ricorsiva di base è:
f (0) <- 0
f (1) <- 1
f (n) <- f (n-1) + f (n-2)
Se valutato direttamente, sarà molto lento. Ma immagina di avere una lista che registra tutti i risultati,
fibs !! n <- f (n)
Poi
┌──────┐ ┌──────┐ ┌──────┐
│ 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) : ..... │
└────────────────────────────────────────┘
Questo è codificato come:
fibn n = fibs !! n
where
fibs = 0 : 1 : map f [2..]
f n = fibs !! (n-1) + fibs !! (n-2)
O anche come
GHCi> let fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
GHCi> take 10 fibs
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
zipWith
crea una lista applicando una determinata funzione binaria agli elementi corrispondenti dei due elenchi a essa assegnati, quindi zipWith (+) [x1, x2, ...] [y1, y2, ...]
è uguale a [x1 + y1, x2 + y2, ...]
.
Un altro modo di scrivere fibs
è con la funzione scanl
:
GHCi> let fibs = 0 : scanl (+) 1 fibs
GHCi> take 10 fibs
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
scanl
crea l'elenco di risultati parziali che foldl
produrrebbe, lavorando da sinistra a destra lungo l'elenco di input. Cioè, scanl f z0 [x1, x2, ...]
è uguale a [z0, z1, z2, ...] where z1 = f z0 x1; z2 = f z1 x2; ...
Grazie alla valutazione lazy, entrambe le funzioni definiscono liste infinite senza calcolarle completamente. Cioè, possiamo scrivere una funzione fib
, recuperando l'ennesimo elemento della sequenza di Fibonacci illimitata:
GHCi> let fib n = fibs !! n -- (!!) being the list subscript operator
-- or in point-free style:
GHCi> let fib = (fibs !!)
GHCi> fib 9
34
Iniziare
REPL online
Il modo più semplice per iniziare a scrivere Haskell è probabilmente andare sul sito web Haskell o provare Haskell e utilizzare il REPL online (read-eval-print-loop) sulla home page. Il REPL online supporta la maggior parte delle funzionalità di base e persino qualche IO. C'è anche un tutorial di base a disposizione che può essere avviato digitando il comando help
. Uno strumento ideale per iniziare a imparare le basi di Haskell e provare alcune cose.
GHC (i)
Per i programmatori che sono pronti a impegnarsi un po 'di più, c'è GHCi , un ambiente interattivo che viene fornito con il Glorious / Glasgow Haskell Compiler . Il GHC può essere installato separatamente, ma questo è solo un compilatore. Per poter installare nuove librerie, devono essere installati anche strumenti come Cabal e Stack . Se stai utilizzando un sistema operativo simile a Unix, l'installazione più semplice è installare Stack usando:
curl -sSL https://get.haskellstack.org/ | sh
Questo installa GHC isolato dal resto del sistema, quindi è facile da rimuovere. Tuttavia, tutti i comandi devono essere preceduti dallo stack
. Un altro approccio semplice è installare una piattaforma Haskell . La piattaforma esiste in due versioni:
- La distribuzione minima contiene solo GHC (per compilare) e Cabal / Stack (per installare e compilare pacchetti)
- La distribuzione completa contiene inoltre strumenti per lo sviluppo del progetto, la profilazione e l'analisi della copertura. È incluso anche un set aggiuntivo di pacchetti ampiamente usati.
Queste piattaforme possono essere installate scaricando un programma di installazione e seguendo le istruzioni o utilizzando il gestore pacchetti della tua distribuzione (nota che questa versione non è garantita per essere aggiornata):
Ubuntu, Debian, Mint:
sudo apt-get install haskell-platform
Fedora:
sudo dnf install haskell-platform
Cappello rosso:
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 con Homebrew:
brew cask install haskell-platform
OSX con MacPort:
sudo port install haskell-platform
Una volta installato, dovrebbe essere possibile avviare GHCi invocando il comando ghci
qualsiasi punto del terminale. Se l'installazione è andata bene, la console dovrebbe avere un aspetto simile
me@notebook:~$ ghci
GHCi, version 6.12.1: http://www.haskell.org/ghc/ :? for help
Prelude>
possibilmente con qualche informazione in più su quali librerie sono state caricate prima del Prelude>
. Ora la console è diventata un REPL Haskell ed è possibile eseguire il codice Haskell come nel REPL online. Per uscire da questo ambiente interattivo, è possibile digitare :q
o :quit
. Per ulteriori informazioni su quali comandi sono disponibili in GHCi, tipo :?
come indicato nella schermata iniziale.
Poiché scrivere sempre le stesse cose ripetutamente su una singola riga non è sempre così, potrebbe essere una buona idea scrivere il codice Haskell nei file. Questi file hanno normalmente .hs
per un'estensione e possono essere caricati nel REPL usando :l
oppure :load
.
Come accennato in precedenza, GHCi è una parte del GHC , che in realtà è un compilatore. Questo compilatore può essere utilizzato per trasformare un file .hs
con codice Haskell in un programma in esecuzione. Poiché un file .hs
può contenere molte funzioni, è necessario definire una funzione main
nel file. Questo sarà il punto di partenza per il programma. Il file test.hs
può essere compilato con il comando
ghc test.hs
questo creerà file oggetto e un eseguibile se non ci sono errori e la funzione main
stata definita correttamente.
Strumenti più avanzati
È già stato menzionato in precedenza come gestore pacchetti, ma lo stack può essere uno strumento utile per lo sviluppo di Haskell in modi completamente diversi. Una volta installato, è in grado di
- installazione (più versioni di) GHC
- creazione di progetti e impalcature
- gestione delle dipendenze
- costruzione e test di progetti
- analisi comparativa
IHaskell è un kernel haskell per IPython e consente di combinare codice (eseguibile) con markdown e notazione matematica.
Primes
Alcune varianti più salienti :
Sotto 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])
Illimitato
Setaccio di Eratostene, utilizzando il pacchetto 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 . ...
Tradizionale
(un setaccio di divisione di prova sub-ottimale)
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..] )
Divisione di prova ottimale
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]
Transitional
Dalla divisione di prova al setaccio di Eratostene:
[n | n <- [2..], []==[i | i <- [2..n-1], j <- [0,i..n], j==n]]
Il codice più corto
nubBy (((>1).).gcd) [2..] -- i.e., nubBy (\a b -> gcd a b > 1) [2..]
nubBy
è anche da Data.List
, come (\\)
.
Dichiarazione dei valori
Possiamo dichiarare una serie di espressioni nel REPL come questo:
Prelude> let x = 5
Prelude> let y = 2 * 5 + x
Prelude> let result = y * 10
Prelude> x
5
Prelude> y
15
Prelude> result
150
Per dichiarare gli stessi valori in un file scriviamo quanto segue:
-- 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
I nomi dei moduli sono in maiuscolo, diversamente dai nomi delle variabili.