Ricerca…


Osservazioni

Logo Haskell

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]

Dimostrazione dal vivo

  • Integral è la classe dei tipi di numeri interi. Gli esempi includono Int e Integer .
  • (Integral a) => inserisce un vincolo sul tipo a per essere in detta classe
  • fac :: a -> a dice che fac è una funzione che accetta un a e restituisce un a
  • product è una funzione che accumula tutti i numeri in una lista moltiplicandoli insieme.
  • [1..n] è una notazione speciale che desugars to enumFromTo 1 n , ed è l'intervallo di numeri 1 ≤ x ≤ n .

Variazione 2

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

Dimostrazione dal vivo

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:

  1. La distribuzione minima contiene solo GHC (per compilare) e Cabal / Stack (per installare e compilare pacchetti)
  2. 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

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



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow