Quelle est la différence entre . (point) et $ (signe dollar)?

709

Quelle est la différence entre le point (.)et le signe dollar ($)?

Si je comprends bien, ils sont tous deux du sucre syntaxique pour ne pas avoir besoin d'utiliser de parenthèses.

Rabarberski
la source

Réponses:

1226

L' $opérateur sert à éviter les parenthèses. Tout ce qui apparaît après lui aura priorité sur tout ce qui précède.

Par exemple, supposons que vous ayez une ligne qui se lit comme suit:

putStrLn (show (1 + 1))

Si vous souhaitez vous débarrasser de ces parenthèses, l'une des lignes suivantes ferait également la même chose:

putStrLn (show $ 1 + 1)
putStrLn $ show (1 + 1)
putStrLn $ show $ 1 + 1

Le principal objectif de l' .opérateur n'est pas d'éviter les parenthèses, mais de chaîner les fonctions. Il vous permet de lier la sortie de tout ce qui apparaît à droite à l'entrée de tout ce qui apparaît à gauche. Cela se traduit généralement également par moins de parenthèses, mais fonctionne différemment.

Revenons au même exemple:

putStrLn (show (1 + 1))
  1. (1 + 1)n'a pas d'entrée et ne peut donc pas être utilisé avec l' .opérateur.
  2. showpeut prendre un Intet retourner un String.
  3. putStrLnpeut prendre un Stringet retourner un IO ().

Vous pouvez enchaîner showpour putStrLnaimer ceci:

(putStrLn . show) (1 + 1)

Si c'est trop de parenthèses à votre goût, supprimez-les avec l' $opérateur:

putStrLn . show $ 1 + 1
Michael Steele
la source
55
En fait, puisque + est aussi une fonction, ne pourriez-vous pas la faire préfixer puis la composer également, comme `putStrLn. spectacle . (+) 1 1 `Non pas que ce soit plus clair, mais je veux dire ... vous pourriez, non?
CodexArcanum
4
@CodexArcanum Dans cet exemple, quelque chose comme putStrLn . show . (+1) $ 1serait équivalent. Vous avez raison, car la plupart (tous?) Des opérateurs d'infixe sont des fonctions.
Michael Steele
79
Je me demande pourquoi personne ne mentionne jamais des utilisations similaires map ($3). Je veux dire, j'utilise surtout $pour éviter les parenthèses aussi, mais ce n'est pas comme si c'était tout pour ça.
Cubic
43
map ($3)est une fonction de type Num a => [(a->b)] -> [b]. Il prend une liste de fonctions prenant un nombre, applique 3 à toutes et recueille les résultats.
cubique
21
Vous devez être prudent lorsque vous utilisez $ avec d'autres opérateurs. "x + f (y + z)" n'est pas la même chose que "x + f $ y + z" car ce dernier signifie en fait "(x + f) (y + z)" (c'est-à-dire que la somme de x et f est traité comme une fonction).
Paul Johnson
186

Ils ont différents types et différentes définitions:

infixr 9 .
(.) :: (b -> c) -> (a -> b) -> (a -> c)
(f . g) x = f (g x)

infixr 0 $
($) :: (a -> b) -> a -> b
f $ x = f x

($)est destiné à remplacer l'application de fonction normale mais à une priorité différente pour éviter les parenthèses. (.)sert à composer deux fonctions ensemble pour créer une nouvelle fonction.

Dans certains cas, ils sont interchangeables, mais ce n'est pas vrai en général. L'exemple typique où ils se trouvent est:

f $ g $ h $ x

==>

f . g . h $ x

En d'autres termes, dans une chaîne de $s, tout sauf le dernier peut être remplacé par.

GS - Excusez-vous auprès de Monica
la source
1
Et si xc'était une fonction? Pouvez-vous ensuite l'utiliser .comme final?
richizy
3
@richizy si vous postulez xdans ce contexte, alors oui - mais alors le "final" s'appliquerait à autre chose x. Si vous ne postulez pas x, ce n'est pas différent d' xêtre une valeur.
GS -
124

Notez également que ($)est la fonction identité spécialisée aux types de fonction . La fonction d'identité ressemble à ceci:

id :: a -> a
id x = x

Alors ($)ressemble à ceci:

($) :: (a -> b) -> (a -> b)
($) = id

Notez que j'ai intentionnellement ajouté des parenthèses supplémentaires dans la signature de type.

Les utilisations de ($)peuvent généralement être éliminées en ajoutant des parenthèses (sauf si l'opérateur est utilisé dans une section). Par exemple: f $ g xdevient f (g x).

Les utilisations de (.)sont souvent légèrement plus difficiles à remplacer; ils ont généralement besoin d'un lambda ou de l'introduction d'un paramètre de fonction explicite. Par exemple:

f = g . h

devient

f x = (g . h) x

devient

f x = g (h x)

J'espère que cela t'aides!

Martijn
la source
"Notez que j'ai intentionnellement ajouté des parenthèses supplémentaires dans la signature de type." Je suis confus ... pourquoi as-tu fait ça?
Mateen Ulhaq
3
@MateenUlhaq Le type de ($) est (a -> b) -> a -> b, qui est le même que (a -> b) -> (a -> b), mais les parenthèses supplémentaires ajoutent ici quelques clarté.
Rudi
2
Oh, je suppose. Je le pensais en fonction de deux arguments ... mais à cause du curry, c'est exactement équivalent à une fonction qui renvoie une fonction.
Mateen Ulhaq
78

($) permet de chaîner des fonctions sans ajouter de parenthèses pour contrôler l'ordre d'évaluation:

Prelude> head (tail "asdf")
's'

Prelude> head $ tail "asdf"
's'

L'opérateur de composition (.)crée une nouvelle fonction sans spécifier les arguments:

Prelude> let second x = head $ tail x
Prelude> second "asdf"
's'

Prelude> let second = head . tail
Prelude> second "asdf"
's'

L'exemple ci-dessus est sans doute illustratif, mais ne montre pas vraiment la commodité d'utiliser la composition. Voici une autre analogie:

Prelude> let third x = head $ tail $ tail x
Prelude> map third ["asdf", "qwer", "1234"]
"de3"

Si nous n'utilisons le troisième qu'une seule fois, nous pouvons éviter de le nommer en utilisant un lambda:

Prelude> map (\x -> head $ tail $ tail x) ["asdf", "qwer", "1234"]
"de3"

Enfin, la composition permet d'éviter le lambda:

Prelude> map (head . tail . tail) ["asdf", "qwer", "1234"]
"de3"
mécanique douce
la source
3
Si le stackoverflow avait une fonction de combinaison, je préférerais que la réponse combine les deux explications précédentes avec l'exemple de cette réponse.
Chris.Q
59

La version courte et douce:

  • ($) appelle la fonction qui est son argument de gauche sur la valeur qui est son argument de droite.
  • (.) compose la fonction qui est son argument de gauche sur la fonction qui est son argument de droite.
ellisbben
la source
29

Une application qui est utile et m'a pris un peu de temps à comprendre à partir de la description très courte à vous apprendre un haskell : Depuis:

f $ x = f x

et entre parenthèses le côté droit d'une expression contenant un opérateur infixe le convertit en une fonction de préfixe, on peut écrire de ($ 3) (4+)façon analogue (++", world") "hello".

Pourquoi est-ce que quelqu'un ferait ça? Pour des listes de fonctions, par exemple. Tous les deux:

map (++", world") ["hello","goodbye"]`

et:

map ($ 3) [(4+),(3*)]

sont plus courts que map (\x -> x ++ ", world") ...ou map (\f -> f 3) .... De toute évidence, ces dernières variantes seraient plus lisibles pour la plupart des gens.

Christoph
la source
14
btw, je déconseille d'utiliser $3sans l'espace. Si Template Haskell est activé, cela sera analysé comme une épissure, alors que $ 3signifie toujours ce que vous avez dit. En général, il semble y avoir une tendance chez Haskell à "voler" des bits de syntaxe en insistant sur le fait que certains opérateurs ont des espaces autour d'eux pour être traités comme tels.
GS - Présentez vos excuses à Monica le
1
Cela m'a pris un certain temps pour comprendre comment les parenthèses fonctionnaient: en.wikibooks.org/wiki/Haskell/…
Casebash
18

Haskell: différence entre .(point) et $(signe dollar)

Quelle est la différence entre le point (.)et le signe dollar ($)?. Si je comprends bien, ils sont tous deux du sucre syntaxique pour ne pas avoir besoin d'utiliser de parenthèses.

Ce ne sont pas des sucres syntaxiques pour ne pas avoir besoin d'utiliser des parenthèses - ce sont des fonctions, - infixées, nous pouvons donc les appeler des opérateurs.

Composez `` (.)et quand l'utiliser.

(.)est la fonction de composition. Donc

result = (f . g) x

revient à créer une fonction qui transmet le résultat de son argument gà f.

h = \x -> f (g x)
result = h x

À utiliser (.)lorsque vous n'avez pas les arguments disponibles pour passer aux fonctions que vous souhaitez composer.

Bonne association associative ($), et quand l'utiliser

($)est une fonction d'application associative à droite avec une faible priorité de liaison. Il calcule donc simplement les choses à sa droite en premier. Donc,

result = f $ g x

est la même que celle-ci, sur le plan de la procédure (ce qui compte puisque Haskell est évalué paresseusement, il commencera à évaluer en fpremier):

h = f
g_x = g x
result = h g_x

ou plus concis:

result = f (g x)

À utiliser ($)lorsque vous avez toutes les variables à évaluer avant d'appliquer la fonction précédente au résultat.

Nous pouvons le voir en lisant la source de chaque fonction.

Lisez la source

Voici la source de (.):

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

Et voici la source de ($):

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

Conclusion

Utilisez la composition lorsque vous n'avez pas besoin d'évaluer immédiatement la fonction. Vous souhaitez peut-être passer la fonction résultant de la composition à une autre fonction.

Utilisez l'application lorsque vous fournissez tous les arguments pour une évaluation complète.

Donc pour notre exemple, il serait sémantiquement préférable de faire

f $ g x

quand nous avons x(ou plutôt, gles arguments de), et faisons:

f . g

quand nous ne le faisons pas.

Aaron Hall
la source
12

... ou vous pourriez éviter les constructions .et $en utilisant le pipelining :

third xs = xs |> tail |> tail |> head

C'est après avoir ajouté la fonction d'assistance:

(|>) x y = y x
user1721780
la source
2
Oui, |> est l'opérateur de pipeline F #.
user1721780
6
Une chose à noter ici, c'est que l' $opérateur de Haskell fonctionne en fait plus comme F # <|qu'il ne le fait |>, généralement dans haskell vous écrivez la fonction ci-dessus comme ceci: third xs = head $ tail $ tail $ xsou peut-être même comme third = head . tail . tail, qui dans la syntaxe de style F # serait quelque chose comme ceci:let third = List.head << List.tail << List.tail
Café électrique
1
Pourquoi ajouter une fonction d'assistance pour que Haskell ressemble à F #? -1
vikingsteve
9
Le retourné $est déjà disponible, et il s'appelle & hackage.haskell.org/package/base-4.8.0.0/docs/…
pat
11

Une excellente façon d'en savoir plus sur n'importe quoi (n'importe quelle fonction) est de se rappeler que tout est fonction! Ce mantra général aide, mais dans des cas spécifiques comme les opérateurs, il aide à se souvenir de cette petite astuce:

:t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c

et

:t ($)
($) :: (a -> b) -> a -> b

N'oubliez pas d'utiliser :tgénéreusement et enveloppez vos opérateurs ()!

lol
la source
11

Ma règle est simple (je suis débutant aussi):

  • ne pas utiliser .si vous souhaitez passer le paramètre (appelez la fonction), et
  • ne pas utiliser $s'il n'y a pas encore de paramètre (composer une fonction)

C'est

show $ head [1, 2]

mais jamais:

show . head [1, 2]
halacsy
la source
3
Bonne heuristique, mais pourrait utiliser plus d'exemples
Zoey Hewll
0

Je pense qu'un court exemple de l'endroit où vous utiliseriez .et non $aiderait à clarifier les choses.

double x = x * 2
triple x = x * 3
times6 = double . triple

:i times6
times6 :: Num c => c -> c

Notez qu'il times6s'agit d'une fonction créée à partir de la composition des fonctions.

Brennan Cheung
la source
0

Toutes les autres réponses sont plutôt bonnes. Mais il y a un détail d'utilisation important sur la façon dont ghc traite $, que le vérificateur de type ghc permet l'instatiarion avec des types plus élevés / quantifiés. Si vous regardez le type de $ idpar exemple, vous constaterez que ça va prendre une fonction dont l'argument est lui-même une fonction polymorphe. De petites choses comme ça ne bénéficient pas de la même flexibilité avec un opérateur de perturbation équivalent. (Cela me fait me demander si $! Mérite le même traitement ou non)

Carter Tazio Schonwald
la source