Was ist der Unterschied zwischen dem Punkt (.)
und dem Dollarzeichen ($)
?
Soweit ich weiß, handelt es sich bei beiden um syntaktischen Zucker, da keine Klammern verwendet werden müssen.
Was ist der Unterschied zwischen dem Punkt (.)
und dem Dollarzeichen ($)
?
Soweit ich weiß, handelt es sich bei beiden um syntaktischen Zucker, da keine Klammern verwendet werden müssen.
Antworten:
Der $
Operator dient zum Vermeiden von Klammern. Alles, was danach erscheint, hat Vorrang vor allem, was vorher kommt.
Angenommen, Sie haben eine Zeile mit der Aufschrift:
putStrLn (show (1 + 1))
Wenn Sie diese Klammern entfernen möchten, würde eine der folgenden Zeilen dasselbe tun:
putStrLn (show $ 1 + 1)
putStrLn $ show (1 + 1)
putStrLn $ show $ 1 + 1
Der Hauptzweck des .
Operators besteht nicht darin, Klammern zu vermeiden, sondern Funktionen zu verketten. Damit können Sie die Ausgabe von allem, was rechts angezeigt wird, mit der Eingabe von allem, was links angezeigt wird, verknüpfen. Dies führt normalerweise auch zu weniger Klammern, funktioniert aber anders.
Zurück zum selben Beispiel:
putStrLn (show (1 + 1))
(1 + 1)
hat keine Eingabe und kann daher nicht mit dem .
Operator verwendet werden.show
kann ein nehmen Int
und ein zurückgeben String
.putStrLn
kann ein nehmen String
und ein zurückgeben IO ()
.Sie können verketten, show
um putStrLn
dies zu mögen:
(putStrLn . show) (1 + 1)
Wenn das zu viele Klammern für Ihren Geschmack sind, entfernen Sie sie mit dem $
Operator:
putStrLn . show $ 1 + 1
putStrLn . show . (+1) $ 1
wäre so etwas gleichwertig. Sie haben insofern Recht, als die meisten (alle?) Infix-Operatoren Funktionen sind.
map ($3)
. Ich meine, ich $
vermeide meistens auch Klammern, aber es ist nicht so, dass das alles ist, wofür sie da sind.
map ($3)
ist eine Funktion des Typs Num a => [(a->b)] -> [b]
. Es nimmt eine Liste von Funktionen, die eine Nummer annehmen, wendet 3 auf alle an und sammelt die Ergebnisse.
Sie haben unterschiedliche Typen und unterschiedliche Definitionen:
infixr 9 .
(.) :: (b -> c) -> (a -> b) -> (a -> c)
(f . g) x = f (g x)
infixr 0 $
($) :: (a -> b) -> a -> b
f $ x = f x
($)
soll die normale Funktionsanwendung ersetzen, jedoch mit einer anderen Priorität, um Klammern zu vermeiden. (.)
dient zum Zusammensetzen von zwei Funktionen, um eine neue Funktion zu erstellen.
In einigen Fällen sind sie austauschbar, dies gilt jedoch im Allgemeinen nicht. Das typische Beispiel, wo sie sind, ist:
f $ g $ h $ x
==>
f . g . h $ x
Mit anderen Worten, in einer Kette von $
s können alle bis auf die letzte durch ersetzt werden.
x
eine Funktion wäre? Können Sie dann .
als letzte verwenden?
x
in diesem Kontext bewerben , dann ja - aber dann würde sich der "letzte" auf etwas anderes als bewerben x
. Wenn Sie sich nicht bewerben x
, unterscheidet es sich nicht x
davon, ein Wert zu sein.
Beachten Sie auch, dass ($)
es sich um die auf Funktionstypen spezialisierte Identitätsfunktion handelt . Die Identitätsfunktion sieht folgendermaßen aus:
id :: a -> a
id x = x
Während ($)
sieht so aus:
($) :: (a -> b) -> (a -> b)
($) = id
Beachten Sie, dass ich der Typensignatur absichtlich zusätzliche Klammern hinzugefügt habe.
Verwendungen von ($)
können normalerweise durch Hinzufügen von Klammern beseitigt werden (es sei denn, der Operator wird in einem Abschnitt verwendet). ZB: f $ g x
wird f (g x)
.
Verwendungen von (.)
sind oft etwas schwerer zu ersetzen; Sie benötigen normalerweise ein Lambda oder die Einführung eines expliziten Funktionsparameters. Zum Beispiel:
f = g . h
wird
f x = (g . h) x
wird
f x = g (h x)
Hoffe das hilft!
($)
Ermöglicht die Verkettung von Funktionen ohne Hinzufügen von Klammern zur Steuerung der Bewertungsreihenfolge:
Prelude> head (tail "asdf")
's'
Prelude> head $ tail "asdf"
's'
Der Compose-Operator (.)
erstellt eine neue Funktion, ohne die Argumente anzugeben:
Prelude> let second x = head $ tail x
Prelude> second "asdf"
's'
Prelude> let second = head . tail
Prelude> second "asdf"
's'
Das obige Beispiel ist wohl illustrativ, zeigt aber nicht wirklich die Bequemlichkeit der Verwendung von Komposition. Hier ist eine andere Analogie:
Prelude> let third x = head $ tail $ tail x
Prelude> map third ["asdf", "qwer", "1234"]
"de3"
Wenn wir den dritten nur einmal verwenden, können wir vermeiden, ihn mit einem Lambda zu benennen:
Prelude> map (\x -> head $ tail $ tail x) ["asdf", "qwer", "1234"]
"de3"
Schließlich lässt uns die Komposition das Lambda vermeiden:
Prelude> map (head . tail . tail) ["asdf", "qwer", "1234"]
"de3"
Eine Anwendung, die nützlich ist und für die ich einige Zeit gebraucht habe, um aus der sehr kurzen Beschreibung bei learn you a haskell herauszufinden : Seit:
f $ x = f x
und die Klammerung der rechten Seite eines Ausdrucks, der einen Infix-Operator enthält, konvertiert ihn in eine Präfixfunktion, zu der man ($ 3) (4+)
analog schreiben kann (++", world") "hello"
.
Warum sollte jemand das tun? Zum Beispiel für Funktionslisten. Beide:
map (++", world") ["hello","goodbye"]`
und:
map ($ 3) [(4+),(3*)]
sind kürzer als map (\x -> x ++ ", world") ...
oder map (\f -> f 3) ...
. Offensichtlich wären die letzteren Varianten für die meisten Menschen besser lesbar.
$3
ohne den Platz zu verwenden. Wenn Template Haskell aktiviert ist, wird dies als Spleiß analysiert, während dies $ 3
immer bedeutet, was Sie gesagt haben. Im Allgemeinen scheint es in Haskell einen Trend zu geben, Syntaxbits zu "stehlen", indem darauf bestanden wird, dass bestimmte Operatoren Leerzeichen um sich haben, die als solche behandelt werden sollen.
Haskell: Unterschied zwischen
.
(Punkt) und$
(Dollarzeichen)Was ist der Unterschied zwischen dem Punkt
(.)
und dem Dollarzeichen($)
? Soweit ich weiß, handelt es sich bei beiden um syntaktischen Zucker, da keine Klammern verwendet werden müssen.
Sie sind kein syntaktischer Zucker, weil sie keine Klammern verwenden müssen - sie sind Funktionen -, die angefügt sind, daher können wir sie Operatoren nennen.
(.)
und wann Sie es verwenden sollen.(.)
ist die Compose-Funktion. Damit
result = (f . g) x
ist die gleiche Funktion wie ein Gebäude, das aufgrund seines Arguments gibt weitergegeben g
auf f
.
h = \x -> f (g x)
result = h x
Verwenden (.)
Sie diese Option, wenn Sie nicht über die verfügbaren Argumente verfügen, um sie an die Funktionen zu übergeben, die Sie erstellen möchten.
($)
und wann er verwendet werden soll($)
ist eine rechtsassoziative Apply-Funktion mit geringer Bindungspriorität. Es berechnet also nur die Dinge rechts davon zuerst. Somit,
result = f $ g x
ist prozedural dasselbe (was wichtig ist, da Haskell träge bewertet wird, wird es f
zuerst zu bewerten beginnen ):
h = f
g_x = g x
result = h g_x
oder genauer:
result = f (g x)
Verwenden ($)
Sie diese Option, wenn Sie alle zu bewertenden Variablen haben, bevor Sie die vorhergehende Funktion auf das Ergebnis anwenden.
Wir können dies sehen, indem wir die Quelle für jede Funktion lesen.
Hier ist die Quelle für (.)
:
-- | Function composition.
{-# INLINE (.) #-}
-- Make sure it has TWO args only on the left, so that it inlines
-- when applied to two functions, even if there is no final argument
(.) :: (b -> c) -> (a -> b) -> a -> c
(.) f g = \x -> f (g x)
Und hier ist die Quelle für ($)
:
-- | Application operator. This operator is redundant, since ordinary
-- application @(f x)@ means the same as @(f '$' x)@. However, '$' has
-- low, right-associative binding precedence, so it sometimes allows
-- parentheses to be omitted; for example:
--
-- > f $ g $ h x = f (g (h x))
--
-- It is also useful in higher-order situations, such as @'map' ('$' 0) xs@,
-- or @'Data.List.zipWith' ('$') fs xs@.
{-# INLINE ($) #-}
($) :: (a -> b) -> a -> b
f $ x = f x
Verwenden Sie die Komposition, wenn Sie die Funktion nicht sofort bewerten müssen. Vielleicht möchten Sie die Funktion, die sich aus der Komposition ergibt, an eine andere Funktion übergeben.
Verwenden Sie die Anwendung, wenn Sie alle Argumente für eine vollständige Bewertung angeben.
In unserem Beispiel wäre dies semantisch vorzuziehen
f $ g x
wenn wir x
(oder besser gesagt g
) Argumente haben und tun:
f . g
wenn wir es nicht tun.
... oder Sie könnten die .
und $
Konstruktionen durch Pipelining vermeiden :
third xs = xs |> tail |> tail |> head
Das ist, nachdem Sie in der Hilfsfunktion hinzugefügt haben:
(|>) x y = y x
$
Betreiber eigentlich mehr wie F # funktioniert 's , <|
als es tut |>
, in der Regel in Haskell würden Sie die obige Funktion wie folgt schreiben: third xs = head $ tail $ tail $ xs
oder vielleicht sogar wie third = head . tail . tail
, was in F # -Stil Syntax etwas so sein würde:let third = List.head << List.tail << List.tail
Eine gute Möglichkeit, mehr über irgendetwas (jede Funktion) zu erfahren, besteht darin, sich daran zu erinnern, dass alles eine Funktion ist! Dieses allgemeine Mantra hilft, aber in bestimmten Fällen wie bei Operatoren hilft es, sich an diesen kleinen Trick zu erinnern:
:t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
und
:t ($)
($) :: (a -> b) -> a -> b
Denken Sie daran, :t
großzügig zu verwenden , und wickeln Sie Ihre Bediener ein ()
!
Meine Regel ist einfach (ich bin auch Anfänger):
.
wenn Sie den Parameter übergeben möchten (Funktion aufrufen), und$
wenn noch kein Parameter vorhanden ist (Funktion erstellen)Das ist
show $ head [1, 2]
aber nie:
show . head [1, 2]
Ich denke, ein kurzes Beispiel dafür, wo Sie verwenden würden .
und nicht, $
würde helfen, die Dinge zu klären.
double x = x * 2
triple x = x * 3
times6 = double . triple
:i times6
times6 :: Num c => c -> c
Beachten Sie, dass dies times6
eine Funktion ist, die aus der Funktionszusammensetzung erstellt wird.
Alle anderen Antworten sind ziemlich gut. Es gibt jedoch ein wichtiges Usability-Detail darüber, wie ghc $ behandelt, das der ghc-Typprüfer die Instatiarion mit höherrangigen / quantifizierten Typen ermöglicht. Wenn Sie sich $ id
zum Beispiel den Typ von ansehen, werden Sie feststellen, dass er eine Funktion annehmen wird, deren Argument selbst eine polymorphe Funktion ist. Kleinigkeiten wie diese haben nicht die gleiche Flexibilität wie ein gleichwertiger verärgerter Bediener. (Das lässt mich tatsächlich fragen, ob $! Die gleiche Behandlung verdient oder nicht)