Was ist der Unterschied zwischen . (Punkt) und $ (Dollarzeichen)?

709

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.

Rabarberski
quelle

Antworten:

1226

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 + 1)hat keine Eingabe und kann daher nicht mit dem .Operator verwendet werden.
  2. showkann ein nehmen Intund ein zurückgeben String.
  3. putStrLnkann ein nehmen Stringund ein zurückgeben IO ().

Sie können verketten, showum putStrLndies 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
Michael Steele
quelle
54
Da + auch eine Funktion ist, können Sie sie nicht mit einem Präfix versehen und dann auch wie putStrLn komponieren. Show . (+) 1 1 `Nicht, dass es klarer ist, aber ich meine ... du könntest, richtig?
CodexArcanum
4
@CodexArcanum In diesem Beispiel putStrLn . show . (+1) $ 1wäre so etwas gleichwertig. Sie haben insofern Recht, als die meisten (alle?) Infix-Operatoren Funktionen sind.
Michael Steele
79
Ich frage mich, warum niemand jemals solche Verwendungen erwähnt map ($3). Ich meine, ich $vermeide meistens auch Klammern, aber es ist nicht so, dass das alles ist, wofür sie da sind.
Cubic
43
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.
Cubic
21
Sie müssen vorsichtig sein, wenn Sie $ mit anderen Operatoren verwenden. "x + f (y + z)" ist nicht dasselbe wie "x + f $ y + z", da letzteres tatsächlich "(x + f) (y + z)" bedeutet (dh die Summe von x und f ist als Funktion behandelt).
Paul Johnson
186

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.

GS - Entschuldige dich bei Monica
quelle
1
Was wäre, wenn xeine Funktion wäre? Können Sie dann .als letzte verwenden?
Richizy
3
@richizy Wenn Sie sich tatsächlich xin 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 xdavon, ein Wert zu sein.
GS - Entschuldigen Sie sich bei Monica
124

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 xwird 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!

Martijn
quelle
"Beachten Sie, dass ich der Typensignatur absichtlich zusätzliche Klammern hinzugefügt habe." Ich bin verwirrt ... warum hast du das getan?
Mateen Ulhaq
3
@MateenUlhaq Der Typ von ($) ist (a -> b) -> a -> b, was mit (a -> b) -> (a -> b) identisch ist, aber die zusätzlichen Klammern fügen hier einige hinzu Klarheit.
Rudi
2
Oh, ich nehme an. Ich habe es als Funktion von zwei Argumenten betrachtet ... aber aufgrund des Currying ist es genau gleichbedeutend mit einer Funktion, die eine Funktion zurückgibt.
Mateen Ulhaq
78

($) 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"
Softmechanik
quelle
3
Wenn der Stapelüberlauf eine Kombinationsfunktion hätte, würde ich die Antwort bevorzugen, die die beiden vorherigen Erklärungen mit dem Beispiel in dieser Antwort kombiniert.
Chris.Q
59

Die kurze und süße Version:

  • ($) Ruft die Funktion, die das linke Argument ist, für den Wert auf, der das rechte Argument ist.
  • (.) setzt die Funktion, die das linke Argument ist, mit der Funktion zusammen, die das rechte Argument ist.
ellisbben
quelle
29

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.

Christoph
quelle
14
Übrigens würde ich davon abraten, $3ohne den Platz zu verwenden. Wenn Template Haskell aktiviert ist, wird dies als Spleiß analysiert, während dies $ 3immer 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.
GS - Entschuldigen Sie sich bei Monica
1
Ich habe eine Weile gebraucht,
Casebash
18

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.

Verfassen Sie, (.)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 gauf 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.

Es gilt der richtige Assoziativ ($)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 fzuerst 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.

Lesen Sie die Quelle

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

Fazit

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.

Aaron Hall
quelle
12

... 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
user1721780
quelle
2
Ja, |> ist der F # -Pipeline-Operator.
user1721780
6
Eine Sache , die hier zu beachten, ist , dass Haskell $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 $ xsoder 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
Elektrischer Kaffee
1
Warum eine Hilfsfunktion hinzufügen, damit Haskell wie F # aussieht? -1
Wikingersteve
9
Das gespiegelte $ist bereits verfügbar und heißt & hackage.haskell.org/package/base-4.8.0.0/docs/…
pat
11

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, :tgroßzügig zu verwenden , und wickeln Sie Ihre Bediener ein ()!

lol
quelle
11

Meine Regel ist einfach (ich bin auch Anfänger):

  • Nicht verwenden, .wenn Sie den Parameter übergeben möchten (Funktion aufrufen), und
  • nicht verwenden, $wenn noch kein Parameter vorhanden ist (Funktion erstellen)

Das ist

show $ head [1, 2]

aber nie:

show . head [1, 2]
Halacsy
quelle
3
Gute Heuristik, könnte aber weitere Beispiele gebrauchen
Zoey Hewll
0

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 times6eine Funktion ist, die aus der Funktionszusammensetzung erstellt wird.

Brennan Cheung
quelle
0

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 $ idzum 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)

Carter Tazio Schönwald
quelle