Ukryte cechy Haskell [zamknięty]

Jakie są mniej znane, ale przydatne funkcje języka programowania Haskell. (Rozumiem, że sam język jest mniej znany, ale pracuj ze mną. Nawet wyjaśnienia prostych rzeczy w Haskell, jak zdefiniowanie ciągu Fibonacciego za pomocą jednej linii kodu, zostaną przeze mnie przegłosowane.)

  • spróbuj ograniczyć odpowiedzi do rdzenia Haskell
  • jedna funkcja na odpowiedź
  • Podaj przykład i krótki opis funkcji, a nie tylko link do dokumentacja
  • Oznacz funkcję za pomocą pogrubionego tytułu jako pierwszej linii
 32
Author: Claudiu, 2008-10-17

25 answers

Mój mózg eksplodował

Jeśli spróbujesz skompilować ten kod:

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

Zostanie wyświetlony komunikat o błędzie:

$ 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
 25
Author: ephemient,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-19 15:10:39

Struktury kontrolne zdefiniowane przez Użytkownika

Haskell nie ma krótkiego operatora trójdzielnego. Wbudowany if-then-else jest zawsze trójkowy i jest wyrażeniem (języki imperatywne mają zazwyczaj ?: = wyrażenie, if=oświadczenie). Jeśli jednak chcesz,

True ? x = const x
False ? _ = id

Zdefiniuje (?) jako operator trójkowy:

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

Będziesz musiał uciekać się do makr w większości innych języków, aby zdefiniować własne zwarte operatory logiczne, ale Haskell jest całkowicie leniwy język, więc po prostu działa.

-- prints "I'm alive! :)"
main = True ? putStrLn "I'm alive! :)" $ error "I'm dead :("
 40
Author: ephemient,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-17 22:02:33

Hoogle

Hoogle jest twoim przyjacielem. Przyznaję, że to nie jest część "rdzenia", więc cabal install hoogle

Teraz już wiesz, jak "jeśli szukasz funkcji wyższego rzędu, to już tam jest" ( komentarz ephemienta ). Ale jak znaleźć tę funkcję? Z 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]
Programista hoogle-google nie jest w stanie samodzielnie pisać swoich programów na papierze, tak jak robi to przy pomocy komputera. Ale on i maszyna razem są zmuszeni nie * być / align = "left" /

Btw, jeśli podobał Ci się hoogle koniecznie sprawdź hlint!

 28
Author: yairchu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:00:29

Twierdzenia Wolne

Phil Wadler wprowadził nas w pojęcie wolnego twierdzenia i od tego czasu nadużywamy ich w Haskell.

Te wspaniałe artefakty systemów typu Hindley-Milner pomagają w rozumowaniu równym, używając parametryczności, aby powiedzieć ci, czego funkcja nie zrobi.

Na przykład istnieją dwa prawa, które każda instancja Functora powinna spełniać:

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

Ale twierdzenie o wolności mówi nam, że nie musimy zadawać sobie trudu udowadniania pierwszego, ale biorąc pod uwagę drugie, jest to "wolne" tylko z podpisu typu!

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

Trzeba być nieco ostrożnym z lenistwem, ale jest to częściowo omówione w oryginale i w nowszym artykule Janis Voigtlaenderna temat twierdzeń wolnych w obecności seq.

 22
Author: Edward KMETT,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-06 17:46:13

Skróty dla operacji common list

Następujące są równoważne:

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

Edit

Ponieważ zażądano więcej szczegółów...

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

concat pobiera listę list i łączy je w jedną listę.

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

map mapuje funkcję nad listą.

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

concatMap jest równoważne (.) concat . map: mapuje funkcję nad listą i łączy wyniki.

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

A Monadma bind operacja, który jest nazywany >>= w Haskell (lub jego sugared do - odpowiednik). Lista, aka [], jest Monad. Jeśli zamienimy [] na m w powyższym:

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

Co jest naturalne dla Monad operacji na liście? Musimy spełniać prawa monad,

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

Możesz sprawdzić, czy te prawa obowiązują, jeśli użyjemy implementacji

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)

W rzeczywistości jest to zachowanie Monad []. Jako demonstracja,

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]
 20
Author: ephemient,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-18 18:26:53

Nestable multiline comments .

{- inside a comment,
     {- inside another comment, -}
still commented! -}
 19
Author: ephemient,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-17 21:49:54

Uogólnione algebraiczne typy danych. Oto przykładowy interpreter, w którym system typów pozwala na pokrycie wszystkich przypadków:

{-# 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
 19
Author: Norman Ramsey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-15 01:54:02

Wzory w wiązaniach najwyższego poziomu

five :: Int
Just five = Just 5

a, b, c :: Char
[a,b,c] = "abc"
Jakie to fajne! Zapisuje ci to wezwanie do fromJust i head od czasu do czasu.
 18
Author: Martijn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-05 08:54:07

Opcjonalny Układ

Do rozdzielania bloków można używać wyrazistych nawiasów i średników zamiast białych znaków (aka layout).

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

... lub równoważnie...

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

... zamiast ...

let x = 40
    y = 2
 in x + y

Ponieważ układ nie jest wymagany, programy Haskell mogą być bezpośrednio produkowane przez inne programy.

 17
Author: Jonathan Tran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-17 13:00:04

seq oraz ($!) tylko Oceń wystarczająco, aby sprawdzić, czy coś nie jest dno.

Poniższy program wyświetli tylko "tam".

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

Dla tych, którzy nie znają Haskella, Haskell nie jest ścisły w ogóle, co oznacza, że argument funkcji jest oceniany tylko wtedy, gdy jest potrzebny.

Na przykład następujące wypisuje "ignorowane" i kończy się sukcesem.

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

seq wiadomo, że zmienia to zachowanie poprzez ocenę do bottom jeśli jego pierwszym argumentem jest bottom.

Na przykład:

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

... lub równoważnie, bez infiksu ...

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

... wybuchnie z błędem na "pierwszy". Nigdy nie wydrukuje "niemożliwego do wydrukowania".

Więc może być trochę zaskakujące, że nawet jeśli seq jest ścisła, nie będzie oceniać czegoś tak, jak oceniają je chętne języki. W szczególności nie będzie próbował wymusić wszystkich dodatnich liczb całkowitych w poniższym programie. Zamiast tego sprawdzi, że [1..] isn ' t bottom( które można znaleźć natychmiast), print "done" I exit.

main = [1..] `seq` print "done"
 16
Author: Jonathan Tran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-17 14:56:24

Operator Fixity

Możesz użyć słów kluczowych infix, infixl lub infixr do zdefiniowania asocjacji operatorów i pierwszeństwa. Przykład zaczerpnięty z referencji :

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

Liczba (od 0 do 9) po infiksie pozwala zdefiniować pierwszeństwo operatora, będąc 9 najsilniejszym. Infiks oznacza brak asocjacji, podczas gdy infiksl kojarzy lewy i infiksr kojarzy prawy.

Pozwala to definiować złożone operatory do wykonywania wysokiego poziomu operacje napisane jako proste wyrażenia.

Zauważ, że możesz również używać funkcji binarnych jako operatorów, jeśli umieścisz je między backtickami:

main = print (a `foo` b)

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

I jako takie, można również zdefiniować pierwszeństwo dla nich:

infixr 4 `foo`
 16
Author: Santiago Palladino,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-17 20:37:53

Unikanie nawiasów

Funkcje (.) i ($) w Prelude mają bardzo wygodne ustawienia, dzięki czemu można uniknąć nawiasów w wielu miejscach. Następujące są równoważne:

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

flip pomaga też, następujące są równoważne:

map (\a -> {- some long expression -}) list
flip map list $ \a ->
    {- some long expression -}
 15
Author: ephemient,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-17 21:55:17

Pretty guards

Prelude definiuje otherwise = True, dzięki czemu pełne warunki ochrony odczytywane są bardzo naturalnie.

fac n
  | n < 1     = 1
  | otherwise = n * fac (n-1)
 15
Author: ephemient,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-17 21:58:41

Wyliczenia W Stylu C

Połączenie najwyższego poziomu dopasowania wzorca i ciągów arytmetycznych daje nam poręczny sposób definiowania kolejnych wartości:

foo : bar : baz : _ = [100 ..]    -- foo = 100, bar = 101, baz = 102
 15
Author: beerboy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-14 14:12:35

Czytelny skład funkcji

Prelude definiuje (.) jako skład funkcji matematycznych, czyli g . f najpierw stosuje f, a następnie stosuje g do wyniku.

Jeśli import Control.Arrow, następujące są równoważne:

g . f
f >>> g

Control.Arrow zapewnia instance Arrow (->), a to jest miłe dla osób, które nie lubią czytać aplikacji funkcyjnej od tyłu.

 13
Author: ephemient,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-17 21:53:10

let 5 = 6 in ... jest ważny Haskell.

 11
Author: Martijn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-08 08:51:26

Listy Nieskończone

Skoro wspomniałeś o Fibonacciego, istnieje bardzo elegancki sposób generowania liczb Fibonacciego z nieskończonej listy, takiej jak ta:

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

Operator @ pozwala na użycie dopasowania wzorca na strukturze 1: tfib, jednocześnie odnosząc się do całego wzorca jako fib.

Zauważ, że Lista pojęć wchodzi w nieskończoną rekurencję, generując nieskończoną listę. Można jednak żądać od niego elementów lub obsługiwać je, o ile żądanie skończonej ilości:

take 10 fib

Możesz również zastosować operację do wszystkich elementów przed ich żądaniem:

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

Dzieje się tak dzięki leniwej ocenie parametrów i list Haskella.

 10
Author: Santiago Palladino,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-17 16:02:12

Elastyczna Specyfikacja importu i eksportu modułów

Importowanie i eksportowanie jest miłe.

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'
 9
Author: ephemient,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-17 21:59:35

Jeśli szukasz listy lub funkcji wyższego rzędu, to już tam jest

W bibliotece standardowej jest wiele wygodnych funkcji wyższego rzędu.

-- 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
 8
Author: ephemient,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-17 21:56:51

Rozumowanie Równościowe

Haskell, będąc czysto funkcjonalnym pozwala odczytać znak równości jako rzeczywisty znak równości (przy braku nie nakładających się wzorców).

Pozwala to na zastąpienie definicji bezpośrednio w kodzie, a pod względem optymalizacji daje dużo swobody kompilatorowi, kiedy coś się dzieje.

Dobry przykład tej formy rozumowania można znaleźć tutaj:

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

Przejawia się to również w formie praw lub reguł oczekiwanych dla ważnych członków instancji, na przykład prawa Monad:

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

Może być często używany do uproszczenia kodu monadycznego.

 8
Author: Edward KMETT,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-06 17:38:53

Lenistwo

Wszechobecne lenistwo oznacza, że można robić takie rzeczy jak definiowanie

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

Ale daje nam również wiele bardziej subtelnych korzyści pod względem składni i rozumowania.

Na przykład, ze względu na ścisłość ML ma do czynienia z ograniczeniem wartości i jest bardzo ostrożny w śledzeniu okrągłych letów, ale w Haskell, możemy pozwolić każdemu letowi być rekurencyjnym i nie mieć potrzeby rozróżniania val i fun. Usuwa to główną brodawkę składniową z języka.

To pośrednio rodzi naszą uroczą klauzulę where, ponieważ możemy bezpiecznie przenieść obliczenia, które mogą lub nie mogą być używane z głównego przepływu sterowania i pozwolić lenistwu zajmować się dzieleniem się wynikami.

Możemy zastąpić (prawie) wszystkie funkcje w stylu ML, które muszą przyjmować () i zwracać wartość, po prostu leniwym obliczeniu wartości. Istnieją powody, aby unikać tego od czasu do czasu, aby uniknąć wycieku przestrzeni z CAFs , ale takie przypadki są rzadkie.

Wreszcie, pozwala na nieograniczoną redukcję eta (\x -> f x można zastąpić f). To sprawia, że programowanie zorientowane na kombinatory dla rzeczy takich jak kombinatory parserów jest o wiele przyjemniejsze niż praca z podobnymi konstrukcjami w ścisłym języku.

To pomaga w rozumowaniu o programach w stylu wolnym od punktów lub o przepisywaniu ich na styl wolny od punktów i zmniejsza szum argumentów.

 6
Author: Edward KMETT,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-07 13:00:51

Lista równoległa

(Specjalna funkcja GHC)

  fibs = 0 : 1 : [ a + b | a <- fibs | b <- tail fibs ]
 6
Author: Dario,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-09 18:25:49

Enhanced pattern matching

  • leniwe wzory
  • Niezbite wzory

    let ~(Just x) = someExpression
    

Zobacz dopasowanie wzorca

 6
Author: Dario,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-02 23:07:56

Wyliczenia

Każdy typ będący instancją Enum może być użyty w ciągu arytmetycznym, a nie tylko liczbach:

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

W tym własne typy danych, po prostu wywołaj z Enum, aby uzyskać domyślną implementację:

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]"
 5
Author: beerboy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-14 14:06:08

Monady

Nie są tak ukryte, ale są po prostu wszędzie, nawet tam, gdzie o nich nie myślisz (listy, może-typy) ...

 3
Author: Dario,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-06 18:28:24