Quel est l'intérêt de «const» dans le prélude Haskell?

92

En regardant à travers le Haskell Prelude, je vois une fonction const :

const x _ = x

Je n'arrive pas à trouver quoi que ce soit de pertinent concernant cette fonction.

À quoi ça sert? Quelqu'un peut-il donner un exemple où cette fonction pourrait être utilisée?

stusmith
la source
10
Un exemple: backgroundColor :: Text -> Colorest pour moibackgroundColor = const White
Zhen le

Réponses:

83

C'est utile pour passer à des fonctions d'ordre supérieur lorsque vous n'avez pas besoin de toute leur flexibilité. Par exemple, l'opérateur de séquence monadique >>peut être défini en termes d'opérateur de liaison monadique comme

x >> y = x >>= const y

C'est un peu plus soigné que d'utiliser un lambda

x >> y = x >>= \_ -> y

et vous pouvez même l'utiliser sans point

(>>) = (. const) . (>>=)

bien que je ne recommande pas particulièrement cela dans ce cas.

Hammar
la source
9
+1. Cela revient également fréquemment lors de l'utilisation de combinateurs d'analyseurs.
Fred Foo
47
Ahh donc c'est plus un «générateur de fonctions» - je l'utilise avec un argument, et cela me donne une fonction (en prenant un argument) qui renvoie toujours une valeur constante. Donc map (const 42) [1..5]résultats [42, 42, 42, 42, 42].
stusmith
2
stusmith: Vous l'avez. constest utile pour appliquer à un seul argument pour produire une fonction là où on en a besoin (comme passer à map).
Conal le
8
@stusmith: Vous pouvez l'utiliser de plusieurs manières intéressantes:head = foldr const (error "Prelude.head: empty list")
rampion le
27

Pour ajouter à l'excellente réponse directe de hammar: les fonctions humbles comme constet idsont vraiment utiles en tant que fonction d'ordre supérieur pour la même raison qu'elles sont fondamentales dans le calcul combinateur SKI .

Non pas que je pense que les fonctions préliminaires de haskell ont été modelées consciemment d'après ce système formel ou quoi que ce soit. C'est juste que créer de riches abstractions dans haskell est très facile, donc vous voyez souvent ces types de choses théoriques devenir pratiquement utiles.

Plug sans vergogne, mais j'ai blogué sur la façon dont l'instance Applicative pour (->)est en fait les combinateurs Set ici , si c'est le genre de chose que vous aimez.K

jberryman
la source
8
Eh bien, les combinateurs SKI ont certainement influencé le Prelude. Je me souviens avoir discuté avec Joe Fasel pour savoir si le combinateur S devait être inclus ou non.
augustss
4
Incidemment, ((->) e)est également la monade du lecteur - avec Readeret autres étant simplement des newtypewrappers - et la askfonction est alors id, donc c'est aussi le Icombinateur. Si vous regardez plutôt originale base de BCKW de Haskell Curry, B, Ket Wsont fmap, returnet joinrespectivement.
CA McCann
1
Le lien du blog dans la réponse est mort. Il devrait maintenant pointer ici: brandon.si/code/…
nsxt
22

Un exemple simple d'utilisation constest Data.Functor.(<$). Avec cette fonction, vous pouvez dire: j'ai ici un foncteur avec quelque chose d'ennuyeux dedans, mais à la place je veux avoir cette autre chose intéressante dedans, sans changer la forme du foncteur. Par exemple

import Data.Functor

42 <$ Just "boring"
--> Just 42

42 <$ Nothing
--> Nothing

"cool" <$ ["nonsense","stupid","uninteresting"]
--> ["cool","cool","cool"]

La définition est:

(<$) :: a -> f b -> f a
(<$) =  fmap . const

ou écrit pas aussi inutile:

cool <$ uncool =  fmap (const cool) uncool

Vous voyez comment constest utilisé ici pour "oublier" l'entrée.

Landei
la source
21

Je n'arrive pas à trouver quoi que ce soit de pertinent concernant cette fonction.

La plupart des autres réponses traitent d'applications relativement ésotériques (du moins pour le nouveau venu) de const. En voici un simple: vous pouvez utiliser constpour vous débarrasser d'un lambda qui prend deux arguments, jette le premier mais fait quelque chose d'intéressant avec le second.

Par exemple, la mise en œuvre suivante (inefficace mais instructive) de length,

length' = foldr (\_ acc -> 1 + acc) 0

peut être réécrit comme

length' = foldr (const (1+)) 0

ce qui est peut-être plus élégant.

L'expression const (1+)est en effet sémantiquement équivalente à \_ acc -> 1 + acc, car elle prend un argument, le jette et renvoie la section (1+) .

jub0bs
la source
4
Il m'a fallu 5 minutes pour comprendre comment cela fonctionne :)
Mukesh Soni
15

Une autre utilisation consiste à implémenter des fonctions membres de classe qui ont un argument factice qui ne doit pas être évalué (utilisé pour résoudre les types ambigus). Exemple qui pourrait être dans Data.bits:

instance Bits Int where
  isSigned = const True
  bitSize  = const wordSize
  ...

En utilisant const, nous disons explicitement que nous définissons des valeurs constantes.

Personnellement, je n'aime pas l'utilisation de paramètres factices, mais s'ils sont utilisés dans une classe, c'est une manière plutôt agréable d'écrire des instances.

Jonas Duregård
la source
Les arguments proxy sont en effet bien meilleurs, et lorsque vous ciblez le GHC récent, les applications de type font parfaitement l'affaire.
dfeuer
2

constpeut être simplement l'implémentation que vous recherchez en conjonction avec d'autres fonctions. Voici un exemple que j'ai découvert.

Disons que nous voulons réécrire une structure de 2-tuples en une autre structure de 2-tuples. Je pourrais l'exprimer ainsi:

((a,b),(c,d))  (a,(c,(5,a)))

Je peux donner une définition simple avec la correspondance de motifs:

f ((a,b),(c,d)) = (a,(c,(5,a)))

Et si je veux une solution inutile (tacite) pour ce genre de réécritures? Un peu de réflexion et de bidouillage plus tard, la réponse est que nous pouvons exprimer toutes les réécritures avec (&&&), const, (.), fst, snd. Notez que (&&&)c'est de Control.Arrow.

La solution de l'exemple utilisant ces fonctions est:

(fst.fst &&& (fst.snd &&& (const 5 &&& fst.fst)))

Notez la similitude avec (a,(c,(5,a))). Et si on remplace &&&par ,? Ensuite, il lit:

(fst.fst, (fst.snd, (const 5, fst.fst)))

Remarquez comment aest le premier élément du premier élément, et c'est ce que les fst.fstprojets. Remarquez comment cest le premier élément du deuxième élément, et c'est ce que les fst.sndprojets. Autrement dit, les variables deviennent le chemin vers leur source.

constnous permet d'introduire des constantes. Intéressant comment le nom s'aligne avec le sens!

Je puis généralisé cette idée avec Applicative de sorte que vous pouvez écrire une fonction dans un style inutile (tant que vous avez une analyse de cas disponible en fonctions, telles que maybe, either, bool). Encore une fois, constjoue le rôle d'introduire des constantes. Vous pouvez voir ce travail dans le package Data.Function.Tacit .

Lorsque vous commencez de manière abstraite, à l'objectif, puis que vous travaillez vers une implémentation, vous pouvez être surpris par les réponses. C'est-à-dire qu'une fonction peut être aussi mystérieuse que n'importe quel rouage d'une machine. Cependant, si vous reculez pour faire apparaître la machine entière, vous pouvez comprendre le contexte dans lequel ce rouage est nécessaire.

Erisco
la source
2

Supposons que vous souhaitiez créer une liste Nothingségale à la longueur d'une chaîne. As constrenvoie son premier argument, quel que soit le second, vous pouvez faire:

listOfNothings :: String -> [Maybe Char]
listOfNothings = (map . const) Nothing

ou, plus explicitement:

listOfNothing st = map (const Nothing) st
A.Saramet
la source
0

Supposons que vous souhaitiez faire pivoter une liste. C'est une façon idiomatique de le faire dans Haskell:

rotate :: Int -> [a] -> [a] rotate _ [] = [] rotate n xs = zipWith const (drop n (cycle xs)) xs

Cette fonction zippe deux tableaux avec la fonction const, le premier étant un tableau cyclique infini, le second étant le tableau avec lequel vous avez commencé.

const agit comme la vérification des limites et utilise le tableau d'origine pour terminer le tableau cyclique.

Voir: Faire pivoter une liste dans Haskell

Célèbres Jameis
la source
0

Je n'arrive pas à trouver quoi que ce soit de pertinent concernant cette fonction.

Supposons que vous souhaitiez générer toutes les sous-séquences d'une liste donnée.

Pour chaque élément de la liste, à un moment donné, vous avez le choix entre True (l'inclure dans la sous-séquence courante) ou False (ne pas l'inclure). Cela peut être fait en utilisant la fonction filterM .

Comme ça:

 λ> import Control.Monad
 λ> :t filterM
 filterM :: Applicative m => (a -> m Bool) -> [a] -> m [a]
 λ> 

Par exemple, nous voulons toutes les sous-séquences de [1..4].

 λ> filterM  (const [True, False])  [1..4]
 [[1,2,3,4],[1,2,3],[1,2,4],[1,2],[1,3,4],[1,3],[1,4],[1],[2,3,4],[2,3],[2,4],[2],[3,4],[3],[4],[]]
 λ> 
jpmarinier
la source