Opérateur de points dans Haskell: besoin de plus d'explications

86

J'essaie de comprendre ce que fait l'opérateur point dans ce code Haskell:

sumEuler = sum . (map euler) . mkList

Le code source complet est ci-dessous.

Ma compréhension

L'opérateur point prend les deux fonctions sumet le résultat de map euleret le résultat de mkListcomme entrée.

Mais, sumn'est-ce pas une fonction, c'est l'argument de la fonction, non? Que se passe-t-il?

Aussi, que (map euler)fait-on?

Code

mkList :: Int -> [Int]
mkList n = [1..n-1]

euler :: Int -> Int
euler n = length (filter (relprime n) (mkList n))

sumEuler :: Int -> Int
sumEuler = sum . (map euler) . mkList
cbrulak
la source

Réponses:

138

En termes simples, .est la composition de fonctions, tout comme en mathématiques:

f (g x) = (f . g) x

Dans votre cas, vous créez une nouvelle fonction, sumEulerqui pourrait également être définie comme ceci:

sumEuler x = sum (map euler (mkList x))

Le style de votre exemple est appelé style "sans point" - les arguments de la fonction sont omis. Cela rend le code plus clair dans de nombreux cas. (Il peut être difficile de grok la première fois que vous le voyez, mais vous vous y habituerez après un certain temps. C'est un idiome courant chez Haskell.)

Si vous êtes toujours confus, il peut être utile de vous rapporter .à quelque chose comme un tube UNIX. Si fla sortie de s devient gl'entrée de, dont la sortie devient hl'entrée de, vous l'écririez sur la ligne de commande comme f < x | g | h. Dans Haskell, .fonctionne comme UNIX |, mais "à l'envers" - h . g . f $ x. Je trouve cette notation très utile pour, par exemple, traiter une liste. Au lieu d'une construction lourde comme map (\x -> x * 2 + 10) [1..10], vous pouvez simplement écrire (+10) . (*2) <$> [1..10]. (Et, si vous ne voulez appliquer cette fonction qu'à une seule valeur, c'est (+10) . (*2) $ 10. Cohérent!)

Le wiki Haskell a un bon article avec plus de détails: http://www.haskell.org/haskellwiki/Pointfree

jrockway
la source
1
Petit problème: le premier extrait de code n'est pas réellement valide Haskell.
SwiftsNamesake
2
@SwiftsNamesake Pour ceux d'entre nous qui ne parlent pas couramment Haskell, voulez-vous simplement dire que le signe égal unique n'est pas significatif ici? (L'extrait aurait donc dû être formaté " f (g x)= (f . g) x"?) Ou autre chose?
user234461
1
@ user234461 Exactement, ouais. Vous en auriez besoin à la ==place si vous vouliez un Haskell standard valide.
SwiftsNamesake
Ce petit extrait en haut n'est que de l'or. Comme les autres réponses ici sont correctes, mais cet extrait vient de cliquer directement intuitivement dans ma tête, ce qui rend inutile la lecture du reste de votre réponse.
Tarick Welling le
24

Le . opérateur compose des fonctions. Par exemple,

a . b

a et b sont des fonctions est une nouvelle fonction qui exécute b sur ses arguments, puis a sur ces résultats. Votre code

sumEuler = sum . (map euler) . mkList

est exactement le même que:

sumEuler myArgument = sum (map euler (mkList myArgument))

mais, espérons-le, plus facile à lire. La raison pour laquelle il y a des parenthèses autour de map euler est que cela rend plus clair qu'il y a 3 fonctions en cours de composition: sum , map euler et mkList - map euler est une fonction unique.

Jesse Rusak
la source
23

sumest une fonction dans Haskell Prelude, pas un argument de sumEuler. Il a le type

Num a => [a] -> a

L'opérateur de composition de fonction . a le type

(b -> c) -> (a -> b) -> a -> c

Donc nous avons

           euler           ::  Int -> Int
       map                 :: (a   -> b  ) -> [a  ] -> [b  ]
      (map euler)          ::                 [Int] -> [Int]
                    mkList ::          Int -> [Int]
      (map euler) . mkList ::          Int ->          [Int]
sum                        :: Num a =>                 [a  ] -> a
sum . (map euler) . mkList ::          Int ->                   Int

Notez qu'il Ints'agit bien d'une instance de la Numclasse de types.

Chris Conway
la source
11

Le . L'opérateur est utilisé pour la composition des fonctions. Tout comme les mathématiques, si vous avez les fonctions f (x) et g (x) f. g devient f (g (x)).

map est une fonction intégrée qui applique une fonction à une liste. En mettant la fonction entre parenthèses, la fonction est traitée comme un argument. Un terme pour cela est curry . Vous devriez chercher cela.

Ce que cela fait, c'est qu'il prend une fonction avec disons deux arguments, il applique l'argument euler. (carte euler) non? et le résultat est une nouvelle fonction, qui ne prend qu'un seul argument.

somme . (carte euler). mkList est fondamentalement une manière sophistiquée de rassembler tout cela. Je dois dire que mon Haskell est un peu rouillé mais peut-être pouvez-vous assembler vous-même cette dernière fonction?

John Leidegren
la source
5

Opérateur de points à Haskell

J'essaie de comprendre ce que fait l'opérateur point dans ce code Haskell:

sumEuler = sum . (map euler) . mkList

Réponse courte

Code équivalent sans points, c'est juste

sumEuler = \x -> sum ((map euler) (mkList x))

ou sans le lambda

sumEuler x = sum ((map euler) (mkList x))

car le point (.) indique la composition de la fonction.

Réponse plus longue

Tout d'abord, simplifions l'application partielle de eulerà map:

map_euler = map euler
sumEuler = sum . map_euler . mkList

Maintenant, nous n'avons que les points. Qu'est-ce que ces points indiquent?

De la source :

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

Ainsi (.)est l' opérateur de composition .

Composer

En mathématiques, nous pourrions écrire la composition des fonctions, f (x) et g (x), c'est-à-dire f (g (x)), comme

(f ∘ g) (x)

qui peut être lu "f composé avec g".

Donc en Haskell, f ∘ g, ou f composé avec g, peut s'écrire:

f . g

La composition est associative, ce qui signifie que f (g (h (x))), écrite avec l'opérateur de composition, peut laisser de côté les parenthèses sans aucune ambiguïté.

Autrement dit, puisque (f ∘ g) ∘ h est équivalent à f ∘ (g ∘ h), nous pouvons simplement écrire f ∘ g ∘ h.

Faire des cercles en arrière

Pour revenir à notre simplification précédente, ceci:

sumEuler = sum . map_euler . mkList

signifie simplement qu'il sumEulers'agit d'une composition non appliquée de ces fonctions:

sumEuler = \x -> sum (map_euler (mkList x))
Salle Aaron
la source
4

L'opérateur point applique la fonction de gauche ( sum) à la sortie de la fonction de droite. Dans votre cas, vous enchaînez plusieurs fonctions ensemble - vous passez le résultat de mkListà (map euler), puis le résultat de cela à sum. Ce site présente une bonne introduction à plusieurs des concepts.

Andy Mikula
la source