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.
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.
Réponses:
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)
n'a pas d'entrée et ne peut donc pas être utilisé avec l' .
opérateur.show
peut prendre un Int
et retourner un String
.putStrLn
peut prendre un String
et retourner un IO ()
.Vous pouvez enchaîner show
pour putStrLn
aimer 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
putStrLn . show . (+1) $ 1
serait équivalent. Vous avez raison, car la plupart (tous?) Des opérateurs d'infixe sont des fonctions.
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.
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.
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.
x
c'était une fonction? Pouvez-vous ensuite l'utiliser .
comme final?
x
dans 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.
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 x
devient 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!
($)
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"
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.
$3
sans l'espace. Si Template Haskell est activé, cela sera analysé comme une épissure, alors que $ 3
signifie 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.
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.
(.)
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.
($)
, 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 f
premier):
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.
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
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, g
les arguments de), et faisons:
f . g
quand nous ne le faisons pas.
... 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
$
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 $ xs
ou 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
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 :t
généreusement et enveloppez vos opérateurs ()
!
Ma règle est simple (je suis débutant aussi):
.
si vous souhaitez 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]
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 times6
s'agit d'une fonction créée à partir de la composition des fonctions.
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 $ id
par 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)