Quelle est la différence entre le point (.)
et le signe dollar ($)
?. Si je comprends bien, ce sont tous deux des sucres syntaxiques pour ne pas avoir besoin d’utiliser des parenthèses.
L'opérateur $
permet d'éviter les parenthèses. Tout ce qui apparaîtra après aura préséance sur tout ce qui vient avant.
Par exemple, disons que vous avez 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
L'objectif principal de l'opérateur .
n'est pas d'éviter les parenthèses, mais de chaîner des fonctions. Il vous permet de lier la sortie de tout ce qui apparaît à droite à celle de tout ce qui apparaît à gauche. Cela entraîne généralement aussi moins de parenthèses, mais fonctionne différemment.
Revenons au même exemple:
putStrLn (show (1 + 1))
(1 + 1)
n'a pas d'entrée et ne peut donc pas être utilisé avec l'opérateur .
.show
peut prendre un Int
et renvoyer un String
.putStrLn
peut prendre une String
et renvoyer une IO ()
.Vous pouvez chaîner show
à putStrLn
comme ceci:
(putStrLn . show) (1 + 1)
Si vous avez trop de parenthèses, supprimez-les avec l'opérateur $
:
putStrLn . show $ 1 + 1
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 avec une priorité différente pour éviter les parenthèses. (.)
permet de composer deux fonctions ensemble pour en créer une nouvelle.
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 la dernière peut être remplacé par .
Notez également que ($)
est la fonction d’identité spécialisée dans les types de fonctions . La fonction d'identité ressemble à ceci:
id :: a -> a
id x = x
Alors que ($)
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). Exemple: f $ g x
devient f (g x)
.
Les utilisations de (.)
sont souvent un peu 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!
($)
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 qu'une troisième 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"
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.Une application utile qui m'a pris du temps à comprendre à partir de la très courte description at learn you a haskell : Depuis:
f $ x = f x
et la parenthèse du côté droit d'une expression contenant un opérateur infix, la convertit en une fonction préfixe, on peut écrire ($ 3) (4+)
de la même manière que (++", world") "hello"
.
Pourquoi quelqu'un ferait-il cela? Pour des listes de fonctions, par exemple. Tous les deux:
map (++", world") ["hello","goodbye"]`
et:
map ($ 3) [(4+),(3*)]
sont plus courtes que map (\x -> x ++ ", world") ...
ou map (\f -> f 3) ...
. Évidemment, ces dernières variantes seraient plus lisibles pour la plupart des gens.
... ou vous pouvez éviter les constructions .
et $
en utilisant pipelining :
third xs = xs |> tail |> tail |> head
C'est après que vous ayez ajouté dans la fonction d'assistance:
(|>) x y = y x
Ma règle est simple (je suis débutant aussi):
.
si vous voulez passer le paramètre (appelez la fonction), et $
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]
Un excellent moyen d'en apprendre davantage sur n'importe quoi (n'importe quelle fonction) est de se rappeler que tout est une fonction! Ce mantra général aide, mais dans des cas spécifiques comme les opérateurs, il est utile de se rappeler ce petit truc:
:t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
et
:t ($)
($) :: (a -> b) -> a -> b
Rappelez-vous simplement d'utiliser :t
généreusement et d'envelopper vos opérateurs dans ()
!
Haskell: différence entre
.
(point) et$
(signe dollar)Quelle est la différence entre le point
(.)
et le signe dollar($)
?. Si je comprends bien, ce sont tous deux des sucres syntaxiques pour ne pas avoir besoin d’utiliser des parenthèses.
Ils sont non un sucre syntaxique pour ne pas avoir besoin d'utiliser des parenthèses - ce sont des fonctions - infixées, nous pouvons donc les appeler opérateurs.
(.)
et quand l'utiliser.(.)
est la fonction de composition. Alors
result = (f . g) x
revient à créer une fonction qui passe le résultat de son argument passé de g
à f
.
h = \x -> f (g x)
result = h x
Utilisez (.)
lorsque vous ne disposez pas des arguments à transmettre aux fonctions que vous souhaitez composer.
($)
, et quand l'utiliser($)
est une fonction d’application associative droite avec une priorité de liaison faible. Donc, il calcule simplement les choses à sa droite en premier. Ainsi,
result = f $ g x
est identique à celui-ci, procéduralement (ce qui compte dans la mesure où Haskell est évalué paresseusement, il commencera par évaluer f
en premier):
h = f
g_x = g x
result = h g_x
ou plus concement:
result = f (g x)
Utilisez ($)
lorsque vous avez toutes les variables à évaluer avant d'appliquer la fonction précédente au résultat.
Nous pouvons le voir en lisant le source de chaque fonction.
Voici le source pour (.)
:
-- | 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 le source pour ($)
:
-- | 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
Utilisez la composition lorsque vous n'avez pas besoin d'évaluer immédiatement la fonction. Peut-être souhaitez-vous transmettre la fonction résultant de la composition à une autre fonction.
Utilisez 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 on a x
(ou plutôt, les arguments de g
), et on fait:
f . g
quand nous ne faisons pas.
Toutes les autres réponses sont très bonnes. Mais il existe un détail important sur la convivialité de la manière dont ghc traite $, que le vérificateur de types ghc permet d’installer instatiarion avec des types de rang/quantifiés supérieurs. Si vous regardez le type de $ id
par exemple, vous constaterez qu’il va falloir prendre une fonction dont l’argument est lui-même une fonction polymorphe. De petites choses comme celle-là n’ont pas la même souplesse avec un opérateur perturbé équivalent. (Cela me fait me demander si $! Mérite le même traitement ou non)
Je pense qu'un court exemple d'utilisation de .
et non de $
aiderait à clarifier les choses.
double x = x * 2
triple x = x * 3
times6 = double . triple
:i times6
times6 :: Num c => c -> c
Notez que times6
est une fonction créée à partir de la composition de fonctions.