Versteckte Funktionen von Haskell [geschlossen]
-
03-07-2019 - |
Frage
Was sind die weniger bekannten, aber nützliche Features der Programmiersprache Haskell. (Ich verstehe die Sprache selbst ist weniger bekannt, aber die Arbeit mit mir. Auch Erklärungen zu den einfachen Dingen in Haskell, wie die Definition der Fibonacci-Folge mit einer Zeile Code, wird von mir bekommen upvoted.)
- Versuchen Sie Antworten auf die Haskell Kern zu begrenzen
- Ein Merkmal pro Antwort
- Geben Sie ein Beispiel und eine kurze Beschreibung der Funktion, nicht nur einen Link zur Dokumentation
- Beschriften Sie die Funktion mit kühnen Titel als erste Zeile
Lösung
Mein Gehirn gerade explodiert
Wenn Sie versuchen, diesen Code zu kompilieren:
{-# LANGUAGE ExistentialQuantification #-}
data Foo = forall a. Foo a
ignorefoo f = 1 where Foo a = f
Sie erhalten diese Fehlermeldung:
$ 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
Andere Tipps
Benutzerdefinierte Kontrollstrukturen
Haskell hat keine Stenografie ternären Operator. Der eingebaute in if
-then
-else
ist immer ternärer und ist ein Ausdruck (imperative Sprachen sind in der Regel ?:
= Ausdruck, if
= Anweisung haben). Wenn Sie wollen, obwohl,
True ? x = const x
False ? _ = id
wird (?)
definiert der ternäre Operator sein:
(a ? b $ c) == (if a then b else c)
Sie müssten Makros in den meisten anderen Sprachen greifen Sie Ihre eigenen Kurzschließen logischen Operatoren zu definieren, aber Haskell ist eine voll faul Sprache, so dass es funktioniert einfach.
-- prints "I'm alive! :)"
main = True ? putStrLn "I'm alive! :)" $ error "I'm dead :("
Hoogle
Hoogle ist dein Freund. Ich gebe zu, es ist nicht Teil des „Kerns“, so cabal install hoogle
Jetzt wissen Sie, wie „wenn Sie sich für eine Funktion höherer Ordnung suchen, es ist schon da“ ( ephemient Kommentar ). Aber wie finden Sie diese Funktion? Mit 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]
Die Hoogle-Google-Programmierer sind nicht in der Lage seine Programme auf Papier zu schreiben, indem er sich die gleiche Art, wie er mit Hilfe des Computers. Aber er und die Maschine zusammen * ein erzwungener nicht gerechnet werden.
Btw, wenn Sie gerne Hoogle sicher sein hlint zu überprüfen!
Free Theoreme
Phil Wadler stellte uns die Vorstellung von einem freien Satz rel="nofollow und wir haben sie in Haskell seitdem zu missbrauchen.
Diese wunderbaren Artefakte von Hindley-Milner-Style-Systemen helfen, indem Sie Parametrizität mit equational Argumentation aus Ihnen sagen, was eine Funktion wird nicht tun.
Zum Beispiel gibt es zwei Gesetze, die jede Instanz Functor erfüllen sollte:
- forall f g. fmap f. fmap g = fmap (f. g)
- fmap id = id
Aber der freie Satz sagt uns, wir kümmern müssen nicht die erste zu beweisen, aber angesichts der zweiten kommt es für ‚frei‘ nur von der Art Unterschrift!
fmap :: Functor f => (a -> b) -> f a -> f b
Sie müssen ein bisschen vorsichtig mit Faulheit sein, aber dies teilweise in dem ursprünglichen Papier bedeckt ist, und in Janis Voigtlaender des neueres Papier auf freie Theoreme in Gegenwart von seq
.
Stenografie für eine gemeinsame Liste Operation
Die folgenden sind äquivalent:
concat $ map f list
concatMap f list
list >>= f
Bearbeiten
Seit mehr Details wurden aufgefordert, ...
concat :: [[a]] -> [a]
concat
nimmt eine Liste von Listen und verkettet sie in einer einzigen Liste.
map :: (a -> b) -> [a] -> [b]
map
bildet eine Funktion über eine Liste.
concatMap :: (a -> [b]) -> [a] -> [b]
concatMap
ist (.) concat . map
äquivalent: eine Funktion, über eine Liste Karte und die Ergebnisse verketten
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
A Monad
hat eine bind Operation, die in >>=
Haskell (oder dessen gezuckerten do
-Äquivalent) aufgerufen wird. Liste, auch bekannt als []
, ist ein Monad
. Wenn wir []
für m
in der oben ersetzen:
instance Monad [] where
(>>=) :: [a] -> (a -> [b]) -> [b]
return :: a -> [a]
Was ist die natürlichste Sache für die Monad
Operationen auf einer Liste zu tun? Wir müssen die Monade Gesetze erfüllen,
return a >>= f == f a
ma >>= (\a -> return a) == ma
(ma >>= f) >>= g == ma >>= (\a -> f a >>= g)
Sie können überprüfen, ob diese Gesetze halten, wenn wir die Umsetzung
verwendeninstance 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)
Das ist in der Tat, das Verhalten von Monad []
. Als Demonstration,
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]
Nestbar mehrzeilige Kommentare .
{- inside a comment,
{- inside another comment, -}
still commented! -}
algebraische Datentypen verallgemeinert. Hier ist ein Beispiel-Interpreter, wo der Typ-System können Sie alle Fälle abdecken:
{-# 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
Muster in Top-Level-Bindungen
five :: Int
Just five = Just 5
a, b, c :: Char
[a,b,c] = "abc"
Wie cool ist das! Speichert Sie diesen Anruf fromJust
und head
ab und zu.
Optional Layout-
Sie können explizite Klammern und Semikolons anstelle von Leerzeichen (aka Layout) Blöcke zu begrenzen.
let {
x = 40;
y = 2
} in
x + y
... oder äquivalent ...
let { x = 40; y = 2 } in x + y
... statt ...
let x = 40
y = 2
in x + y
Da das Layout nicht erforderlich ist, können Haskell Programme ohne weiteres von anderen Programmen erzeugt werden.
seq
und ($!)
nur genug bewerten überprüfen dass etwas nicht unten.
Das folgende Programm wird nur drucken "dort".
main = print "hi " `seq` print "there"
Für jene nicht vertraut mit Haskell, Haskell ist nicht streng im Allgemeinen, was bedeutet, dass ein Argument eine Funktion wird nur ausgewertet, wenn es gebraucht wird.
Zum Beispiel können die folgenden Drucke „ignoriert“ und enden mit Erfolg.
main = foo (error "explode!")
where foo _ = print "ignored"
seq
ist bekannt, dass das Verhalten zu ändern, indem Sie nach unten zu überprüfen sein erstes Argument ist unten.
Zum Beispiel:
main = error "first" `seq` print "impossible to print"
... oder äquivalent, ohne Infix ...
main = seq (error "first") (print "impossible to print")
... mit einem Fehler auf „ersten“ sprengen. Es wird nie „unmöglich drucken“.
drucken So könnte es ein wenig überraschend sein, dass, obwohl seq
streng ist, wird es nicht etwas, die Art und Weise eifrig Sprachen auswerten bewerten. Insbesondere werden versuchen, es nicht alle positiven ganzen Zahlen in dem folgenden Programm zu erzwingen. Stattdessen wird es prüfen, ob [1..]
nicht unten (die sofort gefunden werden kann), print „done“, und beenden.
main = [1..] `seq` print "done"
Operator Unveränderlichkeit
Sie können das Infix, infixl oder infixr Schlüsselwörter Operatoren definieren Assoziativität und Vorrang. Beispiel aus dem Referenz :
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
Die Zahl (0 bis 9) nach dem Infix ermöglicht es Ihnen, den Vorrang des Operators zu definieren, wobei 9 die stärksten. Infix bedeutet keine Assoziativität, während infixl Mitarbeiter verlassen und infixr Mitarbeiter rechts.
Auf diese Weise können Sie komplexe Operatoren definieren hohe Operationen als einfache Ausdrücke geschrieben zu tun.
Beachten Sie, dass Sie auch binäre Funktionen als Operatoren verwenden können, wenn man sie zwischen Backticks platzieren:
main = print (a `foo` b)
foo :: Int -> Int -> Int
foo a b = a + b
Und als solche, können Sie auch Vorrang für sie definieren:
infixr 4 `foo`
Vermeiden von Klammern
Die (.)
und ($)
Funktionen in Prelude
haben sehr bequem fixities, so dass Sie Klammern an vielen Stellen vermeiden. Die folgenden sind äquivalent:
f (g (h x))
f $ g $ h x
f . g $ h x
f . g . h $ x
flip
hilft auch, die sind äquivalent:
map (\a -> {- some long expression -}) list
flip map list $ \a ->
{- some long expression -}
Pretty Wächter
Prelude
definiert otherwise = True
, so dass komplette Abdeckung Bedingungen sehr natürlich lesen.
fac n
| n < 1 = 1
| otherwise = n * fac (n-1)
C-Style Aufzählungen
Die Kombination von Top-Level-Pattern-Matching und arithmetische Folgen geben uns eine praktische Möglichkeit, aufeinanderfolgende Werte zu definieren:
foo : bar : baz : _ = [100 ..] -- foo = 100, bar = 101, baz = 102
Lesbare Funktion Zusammensetzung
Prelude
definiert (.)
mathematische Funktion Zusammensetzung zu sein; das heißt, g . f
ersten f
gilt, dann gilt g
auf das Ergebnis.
Wenn Sie import Control.Arrow
, das sind äquivalent:
g . f
f >>> g
Control.Arrow
bietet eine instance Arrow (->)
, und das ist gut für Leute, die nicht gerne rückwärts Funktion Anwendung lesen.
let 5 = 6 in ...
gilt Haskell.
Infinite Listen
Da Sie Fibonacci erwähnt, gibt es eine sehr elegante Art und Weise von Erzeugungs Fibonacci-Zahlen aus einer unendlichen Liste wie folgt aus:
fib@(1:tfib) = 1 : 1 : [ a+b | (a,b) <- zip fib tfib ]
Der Operator @ können Sie Muster auf dem 1 passend verwenden: tfib Struktur, während immer noch auf das gesamte Muster als fib Bezug genommen wird.
Beachten Sie, dass das Verständnis Liste tritt in eine Endlosschleife, eine unendliche Liste zu erzeugen. Sie können jedoch Elemente von ihm verlangen, oder betreiben sie, solange Sie eine endliche Menge anfordern:
take 10 fib
Sie können auch eine Operation auf alle Elemente anwenden, bevor sie Ihr Interesse an:
take 10 (map (\x -> x+1) fib)
Dies ist dank Haskells faul Auswertung von Parametern und Listen.
Flexible Spezifikation des Moduls Importe und Exporte
Importieren und Exportieren von schön ist.
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'
Wenn Sie sich für eine Liste oder Funktion höherer Ordnung, es ist schon da
suchenEs gibt sooo viele Komfort und Funktionen höherer Ordnung in der Standardbibliothek.
-- 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
Equational Reasoning
Haskell, wobei rein funktional ermöglicht es Ihnen, ein Gleichheitszeichen als reale Gleichheitszeichen (in Abwesenheit von nicht-überlappenden Mustern).
lesenAuf diese Weise können Sie Definitionen direkt in den Code ersetzen, und in Bezug auf die Optimierung gibt viel Spielraum für den Compiler darüber, wann Sachen passiert.
Ein gutes Beispiel für diese Form der Argumentation kann hier gefunden werden:
http://www.haskell.org/pipermail /haskell-cafe/2009-March/058603.html
Dies äußert sich auch gut in Form von Gesetzen oder Regeln Pragmas für gültige Mitglieder einer Instanz zu erwarten, zum Beispiel die Monade Gesetze:
- returrn a >> = f == f a
- m >> = Rückkehr == m
- (m >> = f) >> = g == m >> = (\ x -> f x >> = g)
kann oft verwendet werden monadischen Code zu vereinfachen.
Laziness
Ubiquitous Faulheit bedeutet, dass Sie Dinge tun können wie definieren
fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
Aber es gibt uns auch viel subtile Vorteile in Bezug auf Syntax und Argumentation.
Zum Beispiel aufgrund Strikt ML mit dem Werteinschränkung , und ist sehr vorsichtig Kreis let Bindungen zu verfolgen, aber in Haskell, können wir jede rekursiv sein lassen lassen und haben keine Notwendigkeit, zwischen val
und fun
zu unterscheiden. Dies beseitigt eine große syntaktische Warze aus der Sprache.
Das gibt indirekt Anlass zu unserer schönen where
Klausel, weil wir sicher Berechnungen bewegen können, die nicht aus dem Hauptkontrollfluss verwendet werden können, oder sie können und Faulheit Deal lassen mit den Ergebnissen zu teilen.
Wir können (fast) alle diese ML Stil Funktionen ersetzen, die () und geben einen Wert zurück, mit nur einer faulen Berechnung des Wertes nehmen müssen. Es gibt Gründe, dabei von Zeit zu Zeit zu vermeiden, undichten Raum zu vermeiden, mit CAFs , aber solche Fälle sind selten.
Schließlich erlaubt es uneingeschränkte eta-Reduktion (\x -> f x
kann mit f ersetzt werden). Dies macht combinator orientierte Programmierung für Dinge wie Parser Kombinatoren viel angenehmer als mit ähnlichen Konstrukten in einer strengen Sprache arbeiten.
Dies hilft Ihnen, wenn es um Programme in Punkt-freien Art Argumentation, oder über sie in Punkt-freien Art Umschreiben und reduziert Argument Rauschen.
Parallel Liste Verständnis
(Special GHC-Funktion)
fibs = 0 : 1 : [ a + b | a <- fibs | b <- tail fibs ]
Verbesserte Mustervergleich
- Faule Muster
-
Irrefutable Muster
let ~(Just x) = someExpression
Siehe Mustervergleich
Aufzählungen
Jede Art, die eine Instanz von ist Enum kann in einer arithmetischen Folge verwendet wird, nicht nur Zahlen:
alphabet :: String
alphabet = ['A' .. 'Z']
Ihre eigenen Datentypen einschließlich, nur von Enum ableiten eine Standardimplementierung zu bekommen:
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]"
Monaden
Sie sind nicht so versteckt, aber sie sind einfach überall, auch dort, wo man von ihnen nicht denkt (Listen, Vielleicht-Typ) ...