Domanda

Quali sono le funzioni meno conosciute ma utili del linguaggio di programmazione Haskell. (Capisco che il linguaggio stesso è meno conosciuto, ma lavoro con me. Anche le spiegazioni delle cose semplici in Haskell, come la definizione della sequenza di Fibonacci con una riga di codice, verranno annullate da me.)

  • Prova a limitare le risposte al core di Haskell
  • Una funzione per risposta
  • Fornisci un esempio e una breve descrizione della funzione, non solo un collegamento alla documentazione
  • Etichetta la funzione usando il titolo in grassetto come prima riga
È stato utile?

Soluzione

Il mio cervello è appena esploso

Se si tenta di compilare questo codice:

{-# LANGUAGE ExistentialQuantification #-}
data Foo = forall a. Foo a
ignorefoo f = 1 where Foo a = f

Verrà visualizzato questo messaggio di errore:

$ ghc Foo.hs

Foo.hs:3:22:
    My brain just exploded.
    I can't handle pattern bindings for existentially-quantified constructors.
    Instead, use a case-expression, or do-notation, to unpack the constructor.
    In the binding group for
        Foo a
    In a pattern binding: Foo a = f
    In the definition of `ignorefoo':
        ignorefoo f = 1
                    where
                        Foo a = f

Altri suggerimenti

Strutture di controllo definite dall'utente

Haskell non ha un operatore sternale stenografico. Il if - then - else incorporato è sempre ternario ed è un'espressione (le lingue imperative tendono ad avere ?: = espressione, if = istruzione). Se vuoi, però,

True ? x = const x
False ? _ = id

definirà (?) come operatore ternario:

(a ? b $ c)  ==  (if a then b else c)

Dovresti ricorrere a macro nella maggior parte delle altre lingue per definire i tuoi operatori logici di corto circuito, ma Haskell è un linguaggio completamente pigro, quindi funziona e basta.

-- prints "I'm alive! :)"
main = True ? putStrLn "I'm alive! :)" $ error "I'm dead :("

Hoogle

Hoogle è tuo amico. Lo ammetto, non fa parte del "core", quindi cabal install hoogle

Ora sai come " se stai cercando una funzione di ordine superiore, è già lì " ( commento dell'ephemient ). Ma come trovi quella funzione? Con hoogle!

$ hoogle "Num a => [a] -> a"
Prelude product :: Num a => [a] -> a
Prelude sum :: Num a => [a] -> a

$ hoogle "[Maybe a] -> [a]"
Data.Maybe catMaybes :: [Maybe a] -> [a]

$ hoogle "Monad m => [m a] -> m [a]"
Prelude sequence :: Monad m => [m a] -> m [a]

$ hoogle "[a] -> [b] -> (a -> b -> c) -> [c]"
Prelude zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]

Il programmatore hoogle-google non è in grado di scrivere i suoi programmi su carta da solo come fa con l'aiuto del computer. Ma lui e la macchina insieme sono costretti a non essere * da non sottovalutare.

A proposito, se ti è piaciuto hoogle assicurati di dare un'occhiata hlint!

Teoremi gratuiti

Phil Wadler ci ha fatto conoscere l'idea di teorema gratuito e da allora li stiamo abusando ad Haskell.

Questi meravigliosi manufatti dei sistemi di tipo Hindley-Milner aiutano con il ragionamento equazionale usando la parametria per dirti su cosa una funzione non .

Ad esempio, ci sono due leggi che ogni istanza di Functor dovrebbe soddisfare:

  1. forall f g. fmap f. fmap g = fmap (f. g)
  2. fmap id = id

Ma il teorema gratuito ci dice che non dobbiamo preoccuparci di provare il primo, ma dato il secondo viene per 'libero' solo dalla firma del tipo!

fmap :: Functor f => (a -> b) -> f a -> f b

Devi essere un po 'attento con la pigrizia, ma questo è parzialmente coperto nel documento originale e in documento più recente sui teoremi liberi in presenza di seq .

Abbreviazione per un'operazione di elenco comune

Sono equivalenti:

concat $ map f list
concatMap f list
list >>= f

Modifica

Poiché sono stati richiesti maggiori dettagli ...

concat :: [[a]] -> [a]

concat prende un elenco di elenchi e li concatena in un unico elenco.

map :: (a -> b) -> [a] -> [b]

map mappa una funzione su un elenco.

concatMap :: (a -> [b]) -> [a] -> [b]

concatMap è equivalente a (.) concat. map : mappa una funzione su un elenco e concatena i risultati.

class Monad m where
    (>>=) :: m a -> (a -> m b) -> m b
    return :: a -> m a

Una Monade ha un'operazione bind , che si chiama > > = in Haskell (o il suo zuccherato fa -equivalent). Elenco, noto anche come [] , è un Monad . Se sostituiamo [] con m in quanto sopra:

instance Monad [] where
    (>>=) :: [a] -> (a -> [b]) -> [b]
    return :: a -> [a]

Qual è la cosa naturale che le operazioni Monade devono fare su un elenco? Dobbiamo soddisfare le leggi della monade,

return a >>= f           ==  f a
ma >>= (\a -> return a)  ==  ma
(ma >>= f) >>= g         ==  ma >>= (\a -> f a >>= g)

Puoi verificare che queste leggi siano valide se utilizziamo l'implementazione

instance Monad [] where
    (>>=) = concatMap
    return = (:[])

return a >>= f  ==  [a] >>= f  ==  concatMap f [a]  ==  f a
ma >>= (\a -> return a)  ==  concatMap (\a -> [a]) ma  ==  ma
(ma >>= f) >>= g  ==  concatMap g (concatMap f ma)  ==  concatMap (concatMap g . f) ma  ==  ma >>= (\a -> f a >>= g)

Questo è, in effetti, il comportamento di Monad [] . Come dimostrazione,

double x = [x,x]
main = do
    print $ map double [1,2,3]
        -- [[1,1],[2,2],[3,3]]
    print . concat $ map double [1,2,3]
        -- [1,1,2,2,3,3]
    print $ concatMap double [1,2,3]
        -- [1,1,2,2,3,3]
    print $ [1,2,3] >>= double
        -- [1,1,2,2,3,3]

Commenti multilinea annidabili .

{- inside a comment,
     {- inside another comment, -}
still commented! -}

Tipi di dati algebrici generalizzati. Ecco un interprete di esempio in cui il sistema dei tipi ti consente di coprire tutti i casi:

{-# LANGUAGE GADTs #-}
module Exp
where

data Exp a where
  Num  :: (Num a) => a -> Exp a
  Bool :: Bool -> Exp Bool
  Plus :: (Num a) => Exp a -> Exp a -> Exp a
  If   :: Exp Bool -> Exp a -> Exp a -> Exp a 
  Lt   :: (Num a, Ord a) => Exp a -> Exp a -> Exp Bool
  Lam  :: (a -> Exp b) -> Exp (a -> b)   -- higher order abstract syntax
  App  :: Exp (a -> b) -> Exp a -> Exp b
 -- deriving (Show) -- failse

eval :: Exp a -> a
eval (Num n)      = n
eval (Bool b)     = b
eval (Plus e1 e2) = eval e1 + eval e2
eval (If p t f)   = eval $ if eval p then t else f
eval (Lt e1 e2)   = eval e1 < eval e2
eval (Lam body)   = \x -> eval $ body x
eval (App f a)    = eval f $ eval a

instance Eq a => Eq (Exp a) where
  e1 == e2 = eval e1 == eval e2

instance Show (Exp a) where
  show e = "<exp>" -- very weak show instance

instance (Num a) => Num (Exp a) where
  fromInteger = Num
  (+) = Plus

Pattern in associazioni di livello superiore

five :: Int
Just five = Just 5

a, b, c :: Char
[a,b,c] = "abc"

Che bello! Ti salva quella chiamata a da Just e head di tanto in tanto.

Layout opzionale

È possibile utilizzare parentesi graffe e punti e virgola espliciti anziché spazi bianchi (aka layout) per delimitare i blocchi.

let {
      x = 40;
      y = 2
     } in
 x + y

... o equivalentemente ...

let { x = 40; y = 2 } in x + y

... invece di ...

let x = 40
    y = 2
 in x + y

Poiché il layout non è richiesto, i programmi Haskell possono essere prodotti in modo diretto da altri programmi.

seq e ($!) valuta solo abbastanza da verificare che qualcosa non sia in fondo.

Il seguente programma stamperà solo " there " ;.

main = print "hi " `seq` print "there"

Per chi non ha familiarità con Haskell, Haskell non è rigoroso in generale, il che significa che un argomento per una funzione viene valutato solo se necessario.

Ad esempio, le seguenti stampe " ignorate " e termina con successo.

main = foo (error "explode!")
  where foo _ = print "ignored"

seq è noto per cambiare quel comportamento valutando in basso se il suo primo argomento è in basso.

Ad esempio:

main = error "first" `seq` print "impossible to print"

... o equivalentemente, senza infisso ...

main = seq (error "first") (print "impossible to print")

... esploderà con un errore al "primo". Non verrà mai stampato "impossibile stampare".

Quindi potrebbe essere un po 'sorprendente che anche se seq sia rigoroso, non valuterà qualcosa come valutano le lingue desiderose. In particolare, non tenterà di forzare tutti gli interi positivi nel seguente programma. Al contrario, controllerà che [1 ..] non sia in basso (che può essere trovato immediatamente), stampa "quot" fatto "ed esce.

main = [1..] `seq` print "done"

Correzione dell'operatore

Puoi usare le infix, infixl o infixr per definire gli operatori associatività e precedenza. Esempio tratto dal riferimento :

main = print (1 +++ 2 *** 3)

infixr  6 +++
infixr  7 ***,///

(+++) :: Int -> Int -> Int
a +++ b = a + 2*b

(***) :: Int -> Int -> Int
a *** b = a - 4*b

(///) :: Int -> Int -> Int
a /// b = 2*a - 3*b
Output: -19

Il numero (da 0 a 9) dopo l'infisso consente di definire la precedenza dell'operatore, essendo 9 il più forte. Infix non significa associatività, mentre infixl associa a sinistra e infixr associa a destra.

Ciò consente di definire operatori complessi per eseguire operazioni di alto livello scritte come espressioni semplici.

Nota che puoi anche usare le funzioni binarie come operatori se le posizioni tra i backtick:

main = print (a `foo` b)

foo :: Int -> Int -> Int
foo a b = a + b

E come tale, puoi anche definire la precedenza per loro:

infixr 4 `foo`

Evitare parentesi

Le funzioni (.) e ($) in Prelude hanno fissaggi molto convenienti, che ti permettono di evitare le parentesi in molti punti. Sono equivalenti:

f (g (h x))
f $ g $ h x
f . g $ h x
f . g . h $ x
Anche

flip aiuta, i seguenti sono equivalenti:

map (\a -> {- some long expression -}) list
flip map list $ \a ->
    {- some long expression -}

Pretty guards

Prelude definisce altrimenti = True , facendo leggere le condizioni di protezione complete in modo molto naturale.

fac n
  | n < 1     = 1
  | otherwise = n * fac (n-1)

Enumerazioni in stile C

La combinazione della corrispondenza dei pattern di livello superiore e delle sequenze aritmetiche ci offre un modo pratico per definire valori consecutivi:

foo : bar : baz : _ = [100 ..]    -- foo = 100, bar = 101, baz = 102

Composizione di funzioni leggibili

Prelude definisce (.) come composizione di funzioni matematiche; cioè g. f prima applica f , quindi applica g al risultato.

Se importi Control.Arrow , equivalgono:

g . f
f >>> g

Control.Arrow fornisce una istanza Arrow (- >) , e questo è utile per le persone a cui non piace leggere l'applicazione di funzioni all'indietro.

let 5 = 6 in ... è Haskell valido.

Elenchi infiniti

Dato che hai menzionato fibonacci, esiste un modo molto elegante di generare numeri di fibonacci da un elenco infinito come questo:

fib@(1:tfib)    = 1 : 1 : [ a+b | (a,b) <- zip fib tfib ]

L'operatore @ ti consente di usare la corrispondenza dei pattern sulla struttura 1: tfib pur facendo riferimento all'intero pattern come fib.

Nota che l'elenco di comprensione entra in una ricorsione infinita, generando un elenco infinito. Tuttavia, è possibile richiedere elementi o gestirli, purché si richieda un importo limitato:

take 10 fib

Puoi anche applicare un'operazione a tutti gli elementi prima di richiederli:

take 10 (map (\x -> x+1) fib)

Questo grazie alla valutazione pigra di Haskell di parametri ed elenchi.

Specifica flessibile di importazioni ed esportazioni di moduli

L'importazione e l'esportazione sono utili.

module Foo (module Bar, blah)  -- this is module Foo, export everything that Bar expored, plus blah

import qualified Some.Long.Name as Short
import Some.Long.Name (name)  -- can import multiple times, with different options

import Baz hiding (blah)  -- import everything from Baz, except something named 'blah'

Se stai cercando un elenco o una funzione di ordine superiore, è già lì

Ci sono così tante comodità e funzioni di ordine superiore nella libreria standard.

-- factorial can be written, using the strict HOF foldl':
fac n = Data.List.foldl' (*) 1 [1..n]
-- there's a shortcut for that:
fac n = product [1..n]
-- and it can even be written pointfree:
fac = product . enumFromTo 1

Ragionamento equazionale

Haskell, essendo puramente funzionale ti permette di leggere un segno di uguale come un vero segno di uguale (in assenza di schemi non sovrapposti).

Ciò ti consente di sostituire le definizioni direttamente nel codice e, in termini di ottimizzazione, offre molto margine di manovra al compilatore su quando accadono cose.

Un buon esempio di questa forma di ragionamento può essere trovato qui:

http://www.haskell.org/pipermail /haskell-cafe/2009-March/058603.html

Ciò si manifesta bene anche sotto forma di leggi o pragmi di REGOLE previsti per i membri validi di un'istanza, ad esempio le leggi della Monade:

  1. ricomincia a > > = f == f a
  2. m > > = return == m
  3. (m > > = f) > > = g == m > > = (\ x - > f x > > = g)

può spesso essere usato per semplificare il codice monadico.

Pigrizia

Pigrizia ubiquitaria significa che puoi fare cose come definire

fibs = 1 : 1 : zipWith (+) fibs (tail fibs)

Ma ci offre anche molti più sottili vantaggi in termini di sintassi e ragionamento.

Ad esempio, a causa della severità ML deve fare i conti con restrizione di valore , ed è molto attento a tenere traccia dei binding circolari let, ma in Haskell possiamo lasciare che ogni let sia ricorsivo e non abbiamo bisogno di distinguere tra val e fun . Questo rimuove una grande verruca sintattica dalla lingua.

Questo indirettamente dà origine alla nostra adorabile clausola where , perché possiamo spostare in sicurezza calcoli che possono o meno essere utilizzati dal flusso di controllo principale e lasciare che la pigrizia si occupi della condivisione dei risultati.

Possiamo sostituire (quasi) tutte quelle funzioni di stile ML che devono prendere () e restituire un valore, con solo un calcolo pigro del valore. Ci sono motivi per evitare di farlo di tanto in tanto per evitare perdite di spazio con CAFs , ma tali i casi sono rari.

Infine, consente una riduzione eta illimitata ( \ x - > f x può essere sostituito con f). Questo rende la programmazione orientata al combinatore per cose come i combinatori parser molto più piacevoli che lavorare con costrutti simili in un linguaggio rigoroso.

Questo ti aiuta a ragionare sui programmi in stile privo di punti o a riscriverli in stile senza punti e riduce il rumore degli argomenti.

Comprensione parallela dell'elenco

(funzione GHC speciale)

  fibs = 0 : 1 : [ a + b | a <- fibs | b <- tail fibs ]

Corrispondenza di modelli ottimizzata

  • Modelli pigri
  • Schemi irrefutabili

    let ~(Just x) = someExpression
    

Vedi corrispondenza di modelli

Enumerazioni

Qualsiasi tipo che sia un'istanza di Enum può essere utilizzato in una sequenza aritmetica, non solo numeri:

alphabet :: String
alphabet = ['A' .. 'Z']

Includendo i propri tipi di dati, basta derivare da Enum per ottenere un'implementazione predefinita:

data MyEnum = A | B | C deriving(Eq, Show, Enum)

main = do
    print $ [A ..]                 -- prints "[A,B,C]"
    print $ map fromEnum [A ..]    -- prints "[0,1,2]"

Monadi

Non sono così nascosti, ma sono semplicemente ovunque, anche dove non ci pensi (Liste, Forse-Tipi) ...

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top