Je me permet de vous partager une expérimentation personnelle sur quelques concepts de Haskell.
Quel programmeur n'a jamais eu la rébarbative tâche d'écrire une fonction de comparaison pour un objet/structure avec beaucoup de champs à comparer ? On se retrouve avec un arbre de if/else, ou autre structure de contrôle. Mes premiers pas en Haskell furent le théâtre d'abominables et naïfs
compare a b = case compare (field1 a) (field1 b) of
EQ -> case compare (field2 a) (field2 b) of
EQ -> case compare (field3 a) (field3 b) of
EQ -> compare (length (field4 a)) (length (field4 b))
ord -> ord
ord -> ord
ord -> ord
J'ai donc mis mon cerveau en branle pour profiter de la richesse de Haskell: généricité, type class, fonctions d'ordre supérieur, composition, applications partielles, inférence de type, etc.
En Haskell, on a la chance d'avoir le type Ordering, qui représente le résultat d'une comparaison par 3 constructeurs, plutôt que des entiers: data Ordering = LT | EQ | GT
qui est une instance de Monoid, ce qui nous permet d'utiliser mappend, et mconcat en particulier, sa généralisation sur les liste: class Monoid a where
mempty :: a
mappend :: a -> a -> a
mconcat :: [a] -> a
instance Monoid Ordering where
mempty = EQ
LT `mappend` _ = LT
EQ `mappend` y = y
GT `mappend` _ = GT
En outre, avec la fonction Data.Ord.comparing, on peut aisément comparer un champ, ou plus génériquement une propriété, des valeurs à comparer: comparing :: Ord a => (b -> a) -> b -> b -> Ordering
Il nous est maintenant possible d'écrire facilement un enchaînement comparaison de propriétés, dont on retourne le résultat dès qu'on a divergence (LT ou GT): compare a b = mconcat [comparing field1 a b, comparing field2 a b, comparing field3 a b, comparing (length . field4) a b]
Mais c'est encore très redondant et surtout généralisable! On aurait qu'à avoir la liste des champs/propriétés à comparer!
Cependant, comme une liste a un type donné, on ne peut pas voir une liste hétérogène.
C'est là qu'intervient les existential types! On va créer une fine enveloppe pour stocker nos propriétés à comparer, en garantissant le type d'entrée et la possibilité de comparer le type de sortie:
data Property a = forall p. Ord p => Prop (a -> p)
Il nous reste alors plus qu'à jouer du mconcat et du comparing pour écrire une fonction permettant de comparer séquentiellement sur une liste de propriété:
compareProps :: [Property a] -> a -> a -> Ordering
compareProps ps a b = mconcat $ map (\(Prop p) -> comparing p a b) ps
On peut maintenant réécrire notre comparaison plus simplement: compare = compareProps [Prop field1, Prop field2, Prop field3, Prop (length . field4)]
Voilà, entre autre chose, ce que peut permettre un puissant système de type. :)
PS: J'ai essayé de faire au mieux, mais ma p'tite présentation est probablement très confuse et impénétrable si on a pas quelques notions de Haskell. Je m'en excuse par avance. /o\
[^] # Re: typage mou
Posté par MsieurHappy . En réponse à la dépêche Quelques nouvelles de Go, le langage de programmation. Évalué à 2.
Quel programmeur n'a jamais eu la rébarbative tâche d'écrire une fonction de comparaison pour un objet/structure avec beaucoup de champs à comparer ? On se retrouve avec un arbre de if/else, ou autre structure de contrôle. Mes premiers pas en Haskell furent le théâtre d'abominables et naïfs
compare a b = case compare (field1 a) (field1 b) ofEQ -> case compare (field2 a) (field2 b) of
EQ -> case compare (field3 a) (field3 b) of
EQ -> compare (length (field4 a)) (length (field4 b))
ord -> ord
ord -> ord
ord -> ord
J'ai donc mis mon cerveau en branle pour profiter de la richesse de Haskell: généricité, type class, fonctions d'ordre supérieur, composition, applications partielles, inférence de type, etc.
En Haskell, on a la chance d'avoir le type Ordering, qui représente le résultat d'une comparaison par 3 constructeurs, plutôt que des entiers:
data Ordering = LT | EQ | GTqui est une instance de Monoid, ce qui nous permet d'utiliser mappend, et mconcat en particulier, sa généralisation sur les liste:
class Monoid a where
mempty :: a
mappend :: a -> a -> a
mconcat :: [a] -> a
instance Monoid Ordering where
mempty = EQ
LT `mappend` _ = LT
EQ `mappend` y = y
GT `mappend` _ = GT
En outre, avec la fonction Data.Ord.comparing, on peut aisément comparer un champ, ou plus génériquement une propriété, des valeurs à comparer:
comparing :: Ord a => (b -> a) -> b -> b -> OrderingIl nous est maintenant possible d'écrire facilement un enchaînement comparaison de propriétés, dont on retourne le résultat dès qu'on a divergence (LT ou GT):
compare a b = mconcat [comparing field1 a b, comparing field2 a b, comparing field3 a b, comparing (length . field4) a b]Mais c'est encore très redondant et surtout généralisable! On aurait qu'à avoir la liste des champs/propriétés à comparer!
Cependant, comme une liste a un type donné, on ne peut pas voir une liste hétérogène.
C'est là qu'intervient les existential types! On va créer une fine enveloppe pour stocker nos propriétés à comparer, en garantissant le type d'entrée et la possibilité de comparer le type de sortie:
data Property a = forall p. Ord p => Prop (a -> p)Il nous reste alors plus qu'à jouer du mconcat et du comparing pour écrire une fonction permettant de comparer séquentiellement sur une liste de propriété:
compareProps :: [Property a] -> a -> a -> OrderingcompareProps ps a b = mconcat $ map (\(Prop p) -> comparing p a b) ps
On peut maintenant réécrire notre comparaison plus simplement:
compare = compareProps [Prop field1, Prop field2, Prop field3, Prop (length . field4)]Le code en question, avec les imports et tout, dans un exemple un peu plus concret: http://media.msieurhappy.net/pub/Properties.hs
Voilà, entre autre chose, ce que peut permettre un puissant système de type. :)
PS: J'ai essayé de faire au mieux, mais ma p'tite présentation est probablement très confuse et impénétrable si on a pas quelques notions de Haskell. Je m'en excuse par avance. /o\