Comment puis-foncteurs de travail en haskell?
Je suis en train d'apprendre Haskell et je suis à travers toutes les bases. Mais maintenant je suis coincé, en essayant d'obtenir ma tête autour de foncteurs.
J'ai lu que "Un foncteur transforme une catégorie dans une autre catégorie". Qu'est-ce que cela signifie?
Je sais que c'est beaucoup demander, mais quelqu'un pourrait-il me donner un plaine anglais explication de foncteurs ou peut-être un cas d'utilisation simple?
- J'ai trouvé Gabriel blog, Le foncteur design pattern, pour être tout à fait bon. Ce n'est pas exactement la plaine de l'anglais, mais vous devez donner une lecture à travers et voir si ça aide.
- Lien de Anton Guryanov: en.wikibooks.org/wiki/Haskell/Category_theory
Vous devez vous connecter pour publier un commentaire.
Floue explication serait qu'un
Functor
est une sorte de conteneur et une fonction associéefmap
qui vous permet de modifier tout ce qui est contenu, étant donné une fonction qui transforme le contenu.Par exemple, des listes de ce type de conteneur, tel que
fmap (+1) [1,2,3,4]
rendements[2,3,4,5]
.Maybe
peut également être fait un foncteur, tels quefmap toUpper (Just 'a')
rendementsJust 'A'
.Le type général de
fmap
montre assez facilement ce qui se passe:Et les versions spécialisées peuvent le rendre plus clair. Voici la liste version:
Et Peut-être la version:
Vous pouvez obtenir de l'information sur la norme
Functor
instances en interrogeant GHCI avec:i Functor
et de nombreux modules de définir plusieurs instances deFunctor
s (et d'autres types de classes.)Merci de ne pas prendre le mot "conteneur" trop au sérieux, bien que.
Functor
s sont un concept défini, mais vous pouvez souvent raison sur elle avec cela floue analogie.Votre meilleur pari dans la compréhension de ce qui se passe est tout simplement la lecture de la définition de chacune des instances, qui devrait vous donner une intuition sur ce qui se passe. À partir de là, il est seulement un petit pas pour vraiment officialiser votre compréhension de la notion. Ce qui doit être ajouté est une clarification de ce que nos "conteneur" est vraiment, et que chaque instance beaucoup satisfaire une paire de simples lois.
IO
). Donc, au début, on peut penser à eux comme une sorte de "calcul" jointe à une valeur, et non pas comme un "conteneur" contenant une valeur.IO
lui-même n'est pas pure Haskell. J'ai trouvé cette explication plus claire: ConsidérerputStrLn :: String -> IO ()
. Cette fonction prendString
et renvoie unIO ()
, ce qui peut être bien comme un calcul, dans ce cas, la sortie destdout
. Le conteneur explication ici n'est pas aussi clair que le calcul de l'explication. Mais ce n'est que mon avis.Const a
foncteur 🙂[]
considéré comme un foncteur? Ça veut dire que je peux visualiser[]
comme foncteur flèche entreHask -> Hask
? Ne plus dire que, comme un foncteur[]
cartes de des objets tels que desa
à[a]
? Mais que signifient-ils pour les[]
foncteur de carte morphisms? Et pourquoi n'est-ce pas un[]
appelé simplement une morphism dans la catégorie des Hask?J'ai accidentellement écrit un
Haskell Foncteurs Tutoriel
Je vais répondre à votre question à l'aide d'exemples, et je vais mettre le types en dessous dans les commentaires.
Regarder pour le modèle dans les types de.
fmap
est une généralisation de l'map
Les foncteurs sont pour vous donner la
fmap
fonction.fmap
fonctionne commemap
, donc nous allons vérifiermap
première:De sorte qu'il utilise la fonction
(subtract 1)
à l'intérieur de la liste. En fait, pour les listes,fmap
fait exactement ce quemap
n'. Nous allons multiplier le tout par 10 cette fois:Je décrirais ce que la cartographie de la fonction qui multiplie par 10 sur la liste.
fmap
travaille également surMaybe
Quoi d'autre puis-je
fmap
plus? Nous allons Peut-être utiliser le type de données, qui dispose de deux types de valeurs,Nothing
etJust x
. (Vous pouvez utiliserNothing
pour représenter un échec à obtenir une réponse tout enJust x
représente une réponse.)OK, donc encore une fois,
fmap
est à l'aide de(+7)
à l'intérieur de le Peut-être.Et nous pouvons fmap autres fonctions.
length
trouve la longueur d'une liste, de sorte que nous pouvons fmap surMaybe [Double]
Fait
length :: [a] -> Int
mais je l'utilise ici sur[Double]
je me suis donc spécialisé elle.Nous allons utiliser
show
à tourner des trucs dans des chaînes de caractères. Secrètement le type réel deshow
estShow a => a -> String
, mais c'est un peu long, et je l'emploie ici sur unInt
, de sorte qu'il est spécialisé pourInt -> String
.aussi, en regardant en arrière à des listes
fmap
fonctionne surEither something
Nous allons l'utiliser sur une structure légèrement différente,
Either
. Les valeurs de typeEither a b
sont soitLeft a
valeurs ouRight b
valeurs. Parfois, nous utiliser pour représenter un succèsRight goodvalue
ou l'échecLeft errordetails
, et parfois juste pour mélanger les valeurs de deux types dans une. De toute façon, le foncteur pour le type de données ne fonctionne que sur leRight
- il laisseLeft
valeurs. Qui a du sens, surtout si vous êtes en utilisant les valeurs de Droite que ceux qui réussissent (et, en fait, nous ne serions pas mesure pour le faire travailler à la fois parce que les types ne sont pas nécessairement les mêmes). Permet d'utiliser le type deEither String Int
comme un exempleIl fait
(5*)
de travail à l'intérieur de l'un ou l'autre, mais pour Eithers, seul leRight
valeurs sont modifiées. Mais nous pouvons le faire dans l'autre sens surEither Int String
, aussi longtemps que la fonction travaille sur des chaînes de caractères. Mettons", cool!"
à la fin de trucs, à l'aide de(++ ", cool!")
.C'est cool surtout pour utiliser
fmap
sur IOAujourd'hui l'un de mes préférés façons d'utiliser fmap est de l'utiliser sur
IO
valeurs pour modifier la valeur de certains IO opération me donne. Nous allons prendre un exemple qui vous permet de taper quelque chose et puis l'imprime tout de suite:On peut écrire que dans une manière qui se sent plus propre à moi:
>>
ne fait qu'une chose après l'autre, mais la raison pour laquelle j'aime c'est parce que>>=
prend la ChaînegetLine
nous a donné et il se nourrissait deputStrLn
qui prend une Chaîne de caractères.Que faire si nous voulions saluer l'utilisateur:
Si on voulait écrire que dans la plus propre façon je suis un peu coincé. J'aurais du écrire
qui est pas plus agréable que la
do
version. En fait, ledo
la notation est-il de sorte que vous n'avez pas à le faire. Mais peutfmap
venir à la rescousse? Oui, il peut.("Hello, "++)
est une fonction que je peux fmap sur le getLine!nous pouvons l'utiliser comme ceci:
Nous peut tirer de ce truc sur quoi que ce soit que nous sommes donné. Nous allons être en désaccord avec le fait que "Vrai" ou "Faux" a été tapé dans:
Ou disons juste rapport de la taille d'un fichier:
Conclusions: Ce n'
fmap
faire, et que faut-il faire pour?Si vous avez été en regardant les modèles dans les types et la réflexion sur les exemples que vous aurez remarqué que les fmap prend une fonction qui fonctionne sur certaines valeurs, et applique cette fonction sur quelque chose qu'on a ou le produit de ces valeurs, en quelque sorte, modifier les valeurs. (par exemple readLn était de lire Bool, il a donc un type de
IO Bool
il y a une valeur Booléenne dans le sens qu'elle produit unBool
, eg2[4,5,6]
aInt
s en elle.)cela fonctionne pour avoir quelque Chose de Liste de la (écrit
[]
),Maybe
,Either String
,Either Int
,IO
et des charges de plus de choses. Nous l'appelons un Foncteur si cela fonctionne d'une manière sensible (il y a quelques règles - plus tard). Le type réel de fmap estmais nous avons l'habitude de remplacer
something
avecf
pour des raisons de concision. Il est tout de même à le compilateur, si:Ont un regard en arrière sur les types et vérifier cela fonctionne toujours - chose à propos de
Either String Int
attentivement - ce qui estf
que le temps?Annexe: Quels sont le Foncteur règles, et pourquoi en avons-nous?
id
est la fonction identité:Voici les règles:
Tout d'abord l'identité de l'identité: Si vous avez la carte la fonction qui ne fait rien, ça ne change rien. Cela semble évident (un grand nombre de règles), mais vous pouvez l'interpréter en disant que
fmap
est seulement autorisé à modifier les valeurs, et non la structure.fmap
n'est pas autorisé à tournerJust 4
enNothing
, ou[6]
en[1,2,3,6]
, ouRight 4
enLeft 4
parce que plus que la modification des données de la structure ou de contexte pour que les données modifiées.J'ai touché à cette règle une fois quand j'ai travaillé sur une interface utilisateur graphique du projet - je voulais être en mesure de modifier les valeurs, mais je ne pouvais pas le faire sans changer la structure sous-jacente. Nul n'aurait vraiment remarqué la différence, car il a eu le même effet, mais la réalisation, il n'a pas obéir à l'foncteur règles m'a fait repenser toute ma conception, et c'est beaucoup plus propre, plus lisse et plus rapide maintenant.
Autre part de la composition: cela signifie que vous pouvez choisir de fmap une seule fonction à la fois, ou fmap les deux en même temps. Si
fmap
quitte la structure/cadre de vos valeurs et modifie simplement avec la fonction de son, il sera de travailler avec cette règle trop.Pourquoi en avons-nous? Assurez-vous
fmap
n'est pas sournoisement faire quoi que ce soit derrière les scènes ou de changer quoi que ce soit nous ne nous attendions pas. Ils ne sont pas appliquées par le compilateur (demander au compilateur de prouver un théorème avant qu'il compile ton code n'est pas juste, et permettrait de ralentir compilation - le programmeur doit vérifier). Cela signifie que vous pouvez tricher, mais c'est un mauvais plan parce que votre code peut donner des résultats inattendus.fmap (* 10)
de sortie: 10 devrait être de 20. Bon mini-tutoriel 🙂putStrLn "What's your name?" >> getLine >>= (\name -> putStrLn ("Hello, " ++ name))
le même queputStrLn "What's your name?" >> getLine >>= (putStrLn . ("Hello, " ++))
? Ce qui est un peu plus agréable.fmap
version dans la vraie vie, cependant, et neimport Data.Functor
(secrètement, je suis presque toujours à l'aide deimport Control.Applicative
pour obtenir<*>
trop) donc je peux écrire commeputStrLn "What's your name?" >> ("Hello, " ++) <$> getLine >>= putStrLn
Il est important de garder à l'écart dans votre tête la distinction entre un foncteur lui-même, et d'une valeur d'un type qui a un foncteur appliquée. Un foncteur est lui-même un constructeur de type, comme
Maybe
,IO
, ou la liste constructeur[]
. Une valeur dans un foncteur est une certaine valeur particulière dans un type avec ce type constructeur appliqué. par exemple,Just 3
est une valeur particulière dans le typeMaybe Int
(ce type est leMaybe
foncteur applique au typeInt
),putStrLn "Hello World"
est une valeur particulière dans le typeIO ()
, et[2, 4, 8, 16, 32]
est une valeur particulière dans le type[Int]
.J'aime à penser d'une valeur dans un type avec un foncteur appliquée comme étant "la même" comme une valeur dans le type de base, mais avec des extra "contexte". Souvent, les gens utilisent un conteneur analogie pour un foncteur, qui fonctionne assez naturellement pour tout un peu de foncteurs mais alors devient de plus en plus un fardeau qu'une aide lorsque vous rencontrez pour vous convaincre que
IO
ou(->) r
est comme un récipient.Donc, si un
Int
représente une valeur entière, puis uneMaybe Int
représente un entier de valeur qui ne peut pas être présent ("ne peut pas être présent" est le "contexte"). Un[Int]
représente un entier de valeur avec un certain nombre de valeurs possibles (c'est la même interprétation de la liste foncteur comme le "non-déterminisme" l'interprétation de la liste monade). UnIO Int
représente un nombre entier dont la valeur exacte dépend de la totalité de l'univers (ou sinon, il représente une valeur entière qui peut être obtenu par l'exécution d'un processus externe). UnChar -> Int
est une valeur entière pour toutChar
valeur ("fonction prenantr
comme argument" est un foncteur pour tout typer
; avecr
commeChar
(->) Char
est le constructeur de type, qui est un foncteur, qui s'applique àInt
devient(->) Char Int
ouChar -> Int
en notation infixe).La seule chose que vous pouvez faire avec un général foncteur est
fmap
, avec le typeFunctor f => (a -> b) -> (f a -> f b)
.fmap
transforme une fonction qui opère sur les valeurs normales dans une fonction qui fonctionne sur les valeurs, avec un contexte supplémentaire ajoutée par un foncteur; exactement ce que cela fait est différent pour chaque foncteur, mais vous pouvez le faire avec tous.Donc, avec le
Maybe
foncteurfmap (+1)
est la fonction qui calcule un peut-être-n'est pas présent entier 1 supérieur à celui de son entrée, éventuellement, n'est pas présent entier. Avec la liste foncteurfmap (+1)
est la fonction qui calcule un non déterministe entier 1 supérieur à celui de son entrée non déterministe entier. Avec leIO
foncteur,fmap (+1)
est la fonction qui calcule un entier 1 supérieur à celui de son entrée nombre entier dont la valeur dépend-sur-le-externe de l'univers. Avec le(->) Char
foncteur,fmap (+1)
est la fonction qui ajoute 1 à un nombre entier qui dépend d'unChar
(quand je nourris uneChar
à la valeur de retour, je reçois 1 de plus que ce que j'aurais obtenu par l'alimentation de la mêmeChar
à la valeur d'origine).Mais en général, pour certaines inconnues foncteur
f
,fmap (+1)
appliquée à une certaine valeur dans laf Int
est "foncteur version" de la fonction(+1)
ordinairesInt
s. Il ajoute 1 à la valeur entière quelle que soit la nature de "contexte" de ce foncteur a.Sur son propre,
fmap
n'est pas forcément très utile. Habituellement, lorsque vous écrivez un programme de béton et de travailler avec un foncteur, vous travaillez avec un particulier foncteur, et vous pensez souvent defmap
que tout ce qu'il ne pour le foncteur. Quand je travaille avec[Int]
, je suis souvent pas de penser à mes[Int]
valeurs non déterministe entiers, je viens de penser à eux comme à des listes d'entiers, et je pense que defmap
de la même manière que je pense demap
.Pourquoi s'embêter avec les foncteurs? Pourquoi ne pas simplement avoir
map
pour les listes,applyToMaybe
pourMaybe
s, etapplyToIO
pourIO
s? Puis tout le monde sais ce qu'ils font et personne n'aurait à comprendre bizarre concepts abstraits comme les foncteurs.La clé est la reconnaissance qu'il y a un beaucoup de foncteurs, à presque tous les types de conteneurs pour commencer (d'où le conteneur analogie pour ce qui foncteurs sont). Chacun d'eux a une opération correspondant à
fmap
, même si nous n'avons pas de foncteurs. Chaque fois que vous écrivez un algorithme uniquement en termes defmap
opération (oumap
, ou quelque chose comme ça pour votre type particulier), alors si vous l'écrivez en termes de foncteurs plutôt qu'à un type particulier, puis il travaille pour tous foncteurs.Il peut aussi servir comme une forme de documentation. Si j'ai la main d'une de mes liste de valeurs à une fonction que vous avez écrit, qui fonctionne sur les listes, il pouvait faire un certain nombre de choses. Mais si j'ai la main sur ma liste à une fonction que vous avez écrit, qui fonctionne sur des valeurs arbitraires foncteur, puis j'ai savoir que la mise en œuvre de votre fonction ne peut pas être en utilisant la liste des fonctionnalités, seulement foncteur fonctionnalités.
Repensant à comment vous pouvez l'utiliser functorish choses traditionnelle de la programmation impérative peut vous aider à voir les avantages. Il y conteneur des types comme les tableaux, les listes, les arbres, etc, vous avez un peu de modèle que vous utilisez pour effectuer une itération sur eux. Il peut être légèrement différente pour les différents conteneurs, et bien que les bibliothèques offrent souvent la norme itération interfaces à cette adresse. Mais vous avez encore jusqu'à la fin de l'écriture d'un peu de la boucle à chaque fois que vous souhaitez effectuer une itération sur eux, et quand ce que vous voulez faire est de calculer un résultat pour chaque élément dans le conteneur et de recueillir tous les résultats que vous finissent généralement le mélange dans la logique de la construction de la nouvelle récipient que vous allez.
fmap
est chaque pour la boucle de ce formulaire, vous aurez à rédiger, triés une fois pour toutes par la bibliothèque des écrivains, avant même de s'asseoir à programme. De Plus il peut également être utilisé avec des choses commeMaybe
et(->) r
qui ne serait probablement pas être considérés comme ayant rien à voir avec une conception cohérente de l'conteneur d'interface dans des langages impératifs.En Haskell, les foncteurs de capturer l'idée d'avoir des conteneurs de "trucs", de sorte que vous pouvez manipuler que des "trucs" sans changer la forme du récipient.
Foncteurs fournir une fonction,
fmap
, qui vous permet de le faire, en prenant une fonction régulière et "lifting" à une fonction de conteneurs d'un type d'élément à l'autre:Par exemple,
[]
, la liste constructeur de type, est un foncteur:et il en est beaucoup d'autres Haskell type de constructeurs, comme
Maybe
etMap Integer
1:Noter que
fmap
n'est pas autorisé à modifier la forme du récipient, de sorte que si par exemple vousfmap
une liste, le résultat a le même nombre d'éléments, et si vousfmap
unJust
il ne peut pas devenir unNothing
. En termes formels, nous exigeons quefmap id = id
, c'est à dire si vousfmap
la fonction identité, rien ne change.Jusqu'à présent, j'ai été en utilisant le terme "conteneur", mais c'est vraiment un peu plus général que cela. Par exemple,
IO
est aussi un foncteur, et ce que nous entendons par "forme" dans ce cas est quefmap
sur unIO
action ne devrait pas modifier les effets secondaires. En fait, toute monade est un foncteur2.Dans la catégorie de la théorie, de foncteurs vous permettent de convertir entre les différentes catégories, mais en Haskell nous n'vraiment une catégorie, souvent appelé Hask. Par conséquent, tous les foncteurs en Haskell convertir de Hask à Hask, de sorte qu'ils sont ce que nous appelons endofunctors (foncteurs d'une catégorie à lui-même).
Dans leur forme la plus simple, les foncteurs sont un peu ennuyeux. Il ya seulement tellement que vous pouvez faire avec une seule opération. Cependant, une fois que vous commencez à ajouter les activités, vous pouvez vous rendre régulièrement des foncteurs de foncteurs applicatifs de monades et les choses sont rapidement beaucoup plus intéressant, mais c'est au-delà de la portée de cette réponse.
1 Mais
Set
est pas, car il ne peut stocker que deOrd
types. Foncteurs doit être en mesure de contenir n'importe quel type.2 pour des raisons historiques,
Functor
n'est pas une super-classe deMonad
, bien que beaucoup de gens pensent qu'il devrait être.Regardons les types.
Mais qu'est-ce que cela signifie?
D'abord,
f
est une variable de type ici, et il représente un type constructeur:f a
est un type;a
est une variable de type permanent pour un certain type.Deuxièmement, étant donné une fonction
g :: a -> b
, vous aurezfmap g :: f a -> f b
. I. e.fmap g
est une fonction, en transformant les choses de typef a
à des choses de typef b
. Avis, nous ne pouvez pas obtenir les choses de typea
nib
ici. La fonctiong :: a -> b
est en quelque sorte fait de travailler sur des choses de typef a
et les transformer en choses de typef b
.Avis que
f
est le même. Que l'autre type de changements.Ça veut dire quoi? Il peut signifier beaucoup de choses.
f
est généralement considéré comme "contenant" des choses. Ensuite,fmap g
permetg
d'agir à l'intérieur de ces conteneurs, sans les casser ouvert. Les résultats sont toujours entourés de "l'intérieur", le typeclassFunctor
ne nous fournissez pas les capacités de les ouvrir, ou coup d'oeil à l'intérieur. Juste une transformation à l'intérieur opaque choses, c'est tout ce que nous avons. Autres fonctionnalités à venir de quelque part d'autre.Notez également qu'il ne dit pas que ces "contenants" de réaliser une simple "chose" de type
a
; il peut y avoir un grand nombre de "choses", "à l'intérieur", mais tout de même typea
.Enfin, tout candidat à un foncteur doit obéir le foncteur lois: