Haskell – Foncteurs

Contrairement à C++, où les foncteurs sont des objets qui se comportent comme des fonctions, ou à des langages comme Lisp ou Scheme dans lesquels un foncteur est une fonction qui se comporte comme un objet, Haskell nomme foncteur une entité en lien avec la théorie des catégories.

Guillaume Taillon, étudiant au baccalauréat en informatique à l'Université de Sherbrooke, m'a écrit ceci en 2016 :

Si vous tenez à faire la distinction entre les foncteurs C++ et Haskell, je pense qu'il serait plus juste de dire qu'en Haskell, Functor est une abstraction utilisée pour appliquer une transformation sur un type.

Plus particulièrement,

class Functor f where
    fmap :: (a -> b) -> f a -> f b

Un type f est un Functor quand (class Functor f where) il implémente fmap. Soit une fonction prenant une fonction de a dans b (a -> b), ce même type f paramètrisé en a (f a) et retournant une nouvelle instance f en b (f b), f n'étant d'aucune façon une fonction. On pourrait voir f a comme étant une contrainte polymorphique sur une classe f avec un template a.

Par exemple, pour transformer le contenu d'une liste de nombre:

Prelude> fmap (+1) [1,2,3]
[2,3,4]

Ici f est du type [a] (liste, qui est un foncteur) et sa variable de type a est contrainte à Num, Num a => [a] et (a -> b) = Num a => (a -> a)

Ou encore, f identique et (a -> b) = Show a => (a -> String)

Prelude> fmap (show) [1,2,3]
["1","2","3"]

En les composant :

Prelude> fmap (show.(+1)) [1,2,3]
["2","3","4"]

Les fonctions ne sont pas exactement générées non plus, mais plutôt curried où elles peuvent être appliquées partiellement.

L'opérateur + reçoit deux paramètres et retourne une valeur, tous du même type a contraint à Num :

Prelude> :t (+)
(+) :: Num a => a -> a -> a

En l'appliquant partiellement, un obtient une nouvelle fonction :

Prelude> :t (+1)
(+1) :: Num a => a -> a

Qu'on peut alors utiliser comme paramètre avec fmap car leur signatures sont compatibles :

Prelude> :t fmap (+1)
fmap (+1) :: (Functor f, Num b) => f b -> f b

Pour faire complet, show :

Prelude> :t show
show :: Show a => a -> String

Prelude> :t fmap (show)
fmap (show) :: (Functor f, Show a) => f a -> f String

Prelude> :t fmap (show.(+1))
fmap (show.(+1)) :: (Functor f, Num a, Show a) => f a -> f String

Techniquement, un Functor au sens décrit par Guillaume est plus près d'une transformation sur un type, réalisée dynamiquement en Haskell et statiquement (par des traits) en C++.

Lectures complémentaires

Quelques liens pour enrichir le propos.


Valid XHTML 1.0 Transitional

CSS Valide !