Salut les zesteux !
TL;DR: Je demande l’avis des zesteux sur ce code https://gist.github.com/Ikyushii/52f797c5647ae94515aa
Il y a quelques temps, je me suis mis à Haskell. Le langage m’intéressait et j’avais envie de creuser. J’ai écrit quelques début de code source avec, en m’attaquant parfois à des gros morceaux (je pense notamment à Netwire et son interface basée sur les Arrows.
Ma prochaine ambition est d’utiliser Yesod pour écrire une application web que j’ai en tête depuis plusieurs année déjà. Et plutôt que m’attaquer au framework directement, j’ai pris le parti de commencer par un autre morceau. Une partie non négligeable de l’application consiste à montrer certaines données et leurs évolution au cours du temps. Et bien pour modéliser ça, j’ai décidé d’écrire une monade ! La première de ma vie o_o. Et maintenant je viens humblement demander l’avis de ceux qui s’y connaissent.
Sans plus attendre… La monade Gradiente
.
1 2 3 4 5 6 7 | newtype Gradiente a v = Gradiente { runGradiente :: a -> v } instance Monad (Gradiente a) where return x = Gradiente $ \_ -> x Gradiente ti >>= f = Gradiente $ \ t -> let y = ti t Gradiente ti' = f y in ti' t |
Le but est donc de représenter une donnée quelconque comme une fonction à un paramètre (dans mon cas, c’est soit une date, soit une distance).
Son implémentation monadique est comme suit. Pour être honnête, je me suis pas mal inspiré de la monade State. J’ai aussi fait une implémentation de la classe Monoid.
1 2 3 | instance Monoid v => Monoid (Gradiente a v) where mempty = return mempty mappend (Gradiente f1) (Gradiente f2) = Gradiente $ \t -> mappend (f1 t) (f2 t) |
Maintenant il faut pouvoir exprimer ces fonctions à un argument. Pour pouvoir utiliser la notation do
, on a besoin de :
1 2 | range :: Gradiente a a range = Gradiente id |
Et du coup on peut écrire des choses cools. Par exemple…
1 2 3 4 | loading_text :: Gradiente Int String loading_text = do t <- range return $ take t "************" |
C’est cool ! Mais de manière amusante c’est un dommage collatérale. Je voulais personnellement exprimer des transformations successives. Pour ça, j’ai d’abord rajouté la notion d’interval.
1 2 3 4 5 6 7 8 9 | data Interval a = After a | Before a | Between a a interval_hit :: Ord a => Interval a -> a -> Bool interval_hit (After t') t = t >= t' interval_hit (Before t') t = t <= t' interval_hit (Between t' t'') t = interval_hit (After t') t && interval_hit (Before t'') t |
Quelques constructeurs (ils paraissent bête en l’état, mais je préfère) :
1 2 3 4 5 6 7 8 | after :: a -> Interval a after = After before :: a -> Interval a before = Before between :: a -> a -> Interval a between = Between |
Et ensuite, j’exprime l’idée d’application « temporelle partielle »
1 2 3 4 5 6 7 8 9 10 11 12 13 | apply :: Ord a => Interval a -> (v -> v) -> v -> Gradiente a v apply i f x = do t <- range let res = if interval_hit i t then f x else x return res fix :: Ord a => Interval a -> v -> v -> Gradiente a v fix i x = apply i (pure x) |
Et avec tout ça je peux m’amuser à écrire des choses comme suit :
1 2 | i :: Gradiente Int Int i = return 0 >>= fix (between 3 8) 5 >>= apply (after 5) (6+) |
Et c’est tout ! Merci d’avoir jeté un coup d’œil. Vos remarques sont les bienvenues :).