Présentation approximative
Dans la programmation fonctionnelle, un foncteur est essentiellement une construction de levage de fonctions unaires ordinaires (c'est-à-dire celles avec un argument) en fonctions entre des variables de nouveaux types. Il est beaucoup plus facile d'écrire et de maintenir des fonctions simples entre des objets simples et d'utiliser des foncteurs pour les soulever, puis d'écrire manuellement des fonctions entre des objets conteneurs complexes. Un autre avantage est d'écrire des fonctions simples une seule fois, puis de les réutiliser via différents foncteurs.
Des exemples de foncteurs incluent les tableaux, les foncteurs "peut-être" et "soit", les futurs (voir par exemple https://github.com/Avaq/Fluture ), et bien d'autres.
Illustration
Considérez la fonction qui construit le nom complet de la personne à partir du prénom et du nom. Nous pourrions le définir comme fullName(firstName, lastName)
fonction de deux arguments, ce qui ne conviendrait cependant pas aux foncteurs qui ne traitent que les fonctions d'un seul argument. Pour y remédier, nous collectons tous les arguments dans un seul objet name
, qui devient désormais le seul argument de la fonction:
// In JavaScript notation
fullName = name => name.firstName + ' ' + name.lastName
Et maintenant, si nous avons beaucoup de personnes dans un tableau? Au lieu de parcourir manuellement la liste, nous pouvons simplement réutiliser notre fonction fullName
via la map
méthode fournie pour les tableaux avec une seule ligne de code courte:
fullNameList = nameList => nameList.map(fullName)
et l'utiliser comme
nameList = [
{firstName: 'Steve', lastName: 'Jobs'},
{firstName: 'Bill', lastName: 'Gates'}
]
fullNames = fullNameList(nameList)
// => ['Steve Jobs', 'Bill Gates']
Cela fonctionnera, chaque fois que chaque entrée dans notre nameList
est un objet fournissant à la fois firstName
et des lastName
propriétés. Mais que se passe-t-il si certains objets ne le font pas (ou ne le sont même pas)? Pour éviter les erreurs et rendre le code plus sûr, nous pouvons encapsuler nos objets dans le Maybe
type (voir par exemple https://sanctuary.js.org/#maybe-type ):
// function to test name for validity
isValidName = name =>
(typeof name === 'object')
&& (typeof name.firstName === 'string')
&& (typeof name.lastName === 'string')
// wrap into the Maybe type
maybeName = name =>
isValidName(name) ? Just(name) : Nothing()
où Just(name)
est un conteneur portant uniquement des noms valides et Nothing()
est la valeur spéciale utilisée pour tout le reste. Maintenant, au lieu d'interrompre (ou d'oublier) pour vérifier la validité de nos arguments, nous pouvons simplement réutiliser (lever) notre fullName
fonction d' origine avec une autre ligne de code, basée à nouveau sur la map
méthode, cette fois fournie pour le type Maybe:
// Maybe Object -> Maybe String
maybeFullName = maybeName => maybeName.map(fullName)
et l'utiliser comme
justSteve = maybeName(
{firstName: 'Steve', lastName: 'Jobs'}
) // => Just({firstName: 'Steve', lastName: 'Jobs'})
notSteve = maybeName(
{lastName: 'SomeJobs'}
) // => Nothing()
steveFN = maybeFullName(justSteve)
// => Just('Steve Jobs')
notSteveFN = maybeFullName(notSteve)
// => Nothing()
Catégorie Théorie
Un foncteur en théorie des catégories est une carte entre deux catégories respectant la composition de leurs morphismes. Dans un langage informatique , la principale catégorie d'intérêt est celle dont les objets sont des types (certains ensembles de valeurs) et dont les morphismes sont des fonctions f:a->b
d'un type a
à un autre b
.
Par exemple, prenez a
pour être le String
type, b
le type Number et f
la fonction mappant une chaîne dans sa longueur:
// f :: String -> Number
f = str => str.length
Ici a = String
représente l'ensemble de toutes les chaînes et b = Number
l'ensemble de tous les nombres. En ce sens, les deux a
et b
représentent des objets dans la catégorie d'ensemble (qui est étroitement liée à la catégorie de types, la différence étant ici inessentielle). Dans la catégorie d'ensemble, les morphismes entre deux ensembles sont précisément toutes les fonctions du premier ensemble au second. Donc, notre fonction de longueur f
ici est un morphisme de l'ensemble de chaînes dans l'ensemble de nombres.
Comme nous ne considérons que la catégorie d'ensemble, les foncteurs pertinents de celle-ci en elle-même sont des cartes envoyant des objets aux objets et des morphismes aux morphismes, qui satisfont certaines lois algébriques.
Exemple: Array
Array
peut signifier beaucoup de choses, mais une seule chose est un Functor - la construction de type, mappant un type a
dans le type [a]
de tous les tableaux de type a
. Par exemple, le Array
foncteur mappe le type String
dans le type [String]
(l'ensemble de tous les tableaux de chaînes de longueur arbitraire) et définit le type Number
dans le type correspondant [Number]
(l'ensemble de tous les tableaux de nombres).
Il est important de ne pas confondre la carte Functor
Array :: a => [a]
avec un morphisme a -> [a]
. Le foncteur mappe simplement (associe) le type a
au type [a]
comme une chose à une autre. Le fait que chaque type soit en fait un ensemble d'éléments n'a aucune importance ici. En revanche, un morphisme est une fonction réelle entre ces ensembles. Par exemple, il y a un morphisme naturel (fonction)
pure :: a -> [a]
pure = x => [x]
qui envoie une valeur dans le tableau à 1 élément avec cette valeur comme entrée unique. Cette fonction ne fait pas partie du Array
Functor! Du point de vue de ce foncteur, pure
c'est juste une fonction comme les autres, rien de spécial.
D'un autre côté, le Array
Functor a sa deuxième partie - la partie morphisme. Qui mappe un morphisme f :: a -> b
dans un morphisme [f] :: [a] -> [b]
:
// a -> [a]
Array.map(f) = arr => arr.map(f)
Voici arr
tout tableau de longueur arbitraire avec des valeurs de type a
, et arr.map(f)
est le tableau de même longueur avec des valeurs de type b
, dont les entrées sont le résultat de l'application f
aux entrées de arr
. Pour en faire un foncteur, les lois mathématiques de mise en correspondance de l'identité à l'identité et des compositions aux compositions doivent tenir, ce qui est facile à vérifier dans cet Array
exemple.