J’imagine que tu parles des GADTs dans OCaml décrits en 7.18 du manuel ? Juste parce que c’est plus facile pour moi, voilà la version Haskell :
data Term t where
Intg :: Int -> Term Int
Add :: Term (Int -> Int -> Int)
App :: (Term (b -> a), Term b) -> Term a
eval :: Term a -> a
eval (Intg n) = n
eval Add = \x y -> x+y
eval (App (f,x)) = (eval f) (eval x)
On peut la charger dans l’interpréteur, et exécuter l’exemple du manuel OCaml :
ghci> eval (App (App (Add, Intg 1), Intg 1))
2
L’exemple définit un AST fonctionnel dont les termes sont soit des entiers, soit des additions d’entiers, soit des applications d’une fonction à un argument. Les ASTs sont couramment utilisés comme représentation interne des compilateurs et autres interpréteurs.
Dans ce cas, on considère l’addition comme un objet de première classe, c’est à dire qu’on peut le passer en argument, le retourner, etc, et on considère aussi qu’une fonction est curryfiée, c’est à dire qu’une fonction de deux arguments est en fait une fonction de un argument qui retourne une fonction de un argument. Ces deux concepts sont à la base de la programmation fonctionnelle. Un terme d’application fonctionne lui aussi de manière fonctionnelle, en prenant en paramètre une fonction (qui n’a qu’un argument, comme toute fonction :p), un argument, et retourne le résultat de cette fonction.
Avec l’aide d’un interpréteur, on comprend facilement ce que fait chaque terme de l’expression :
ghci> :t Intg 1
Intg 1 :: Term Int
-- La construction d’un terme entier est triviale
ghci> :t App (Add, Intg 1)
App (Add, Intg 1) :: Term (Int -> Int)
-- On construit un terme qui représente l’addition avec 1, en Haskell ce serait (+1)
-- On voit que le résultat est un terme-fonction qui prend un argument entier, et retourne un entier
ghci> :t App (App (Add, Intg 1), Intg 1)
App (App (Add, Intg 1), Intg 1) :: Term Int
-- On utilise le résultat précédent (qui était un terme-fonction) en lui passant un entier
Utiliser les GADTs va permettre de vérifier à la compilaition qu’on ne construise pas de terme erronés :
ghci> :t Intg 1.0
-- Erreur : on ne peut pas convertir implicitement 1.0 en Int
ghci> :t App (Add, Add)
-- Erreur : les paramètres de App doivent être de type b->a et b, ici ils sont identiques
Il y a d’autres applications que cet usage trivial de vérification d’arbres bien formés, exemple qu’on ferait en POO avec des classes, et les design patterns adéquats pour l’évaluation.
De façon générale, mais ce n’est que mon avis, les fonctions avancées des langages fonctionnels sont surtout intéressantes parce qu’elles permettent d’écrire rapidement et de façon très concise (surtout en Haskell) du code sûr et performant. Le cas d’utilisation typique est quand on travaille dans des startups, dans la recherche (scientifique ou R&D), dans lesquels on doit avoir rapidement un code qui marche pour montrer des nouveaux concepts (en gros des cas où l’important ce n’est pas le code mais ce qu’il permet de montrer). C’est mon cas, l’informatique n’est absolument pas mon domaine, juste un outil dont je me sert pour une partie de mon boulot (simulations numériques, affichages prouvables, visualisations, traitement de données). Je n’ai pas le temps de débugger, d’avoir des erreurs à l’exécution (ou du code lent de par les vérifications à l’exécution), et mon code ne sera lu ou utilisé que par un nombre nul ou très très réduit de gens, ce qui compte c’est de vérifier les idées.
[^] # Re: langage fonctionnel
Posté par neil . En réponse au journal Ada, langage et ressources. Évalué à 2.
J’imagine que tu parles des GADTs dans OCaml décrits en 7.18 du manuel ? Juste parce que c’est plus facile pour moi, voilà la version Haskell :
On peut la charger dans l’interpréteur, et exécuter l’exemple du manuel OCaml :
L’exemple définit un AST fonctionnel dont les termes sont soit des entiers, soit des additions d’entiers, soit des applications d’une fonction à un argument. Les ASTs sont couramment utilisés comme représentation interne des compilateurs et autres interpréteurs.
Dans ce cas, on considère l’addition comme un objet de première classe, c’est à dire qu’on peut le passer en argument, le retourner, etc, et on considère aussi qu’une fonction est curryfiée, c’est à dire qu’une fonction de deux arguments est en fait une fonction de un argument qui retourne une fonction de un argument. Ces deux concepts sont à la base de la programmation fonctionnelle. Un terme d’application fonctionne lui aussi de manière fonctionnelle, en prenant en paramètre une fonction (qui n’a qu’un argument, comme toute fonction :p), un argument, et retourne le résultat de cette fonction.
Avec l’aide d’un interpréteur, on comprend facilement ce que fait chaque terme de l’expression :
Utiliser les GADTs va permettre de vérifier à la compilaition qu’on ne construise pas de terme erronés :
Il y a d’autres applications que cet usage trivial de vérification d’arbres bien formés, exemple qu’on ferait en POO avec des classes, et les design patterns adéquats pour l’évaluation.
De façon générale, mais ce n’est que mon avis, les fonctions avancées des langages fonctionnels sont surtout intéressantes parce qu’elles permettent d’écrire rapidement et de façon très concise (surtout en Haskell) du code sûr et performant. Le cas d’utilisation typique est quand on travaille dans des startups, dans la recherche (scientifique ou R&D), dans lesquels on doit avoir rapidement un code qui marche pour montrer des nouveaux concepts (en gros des cas où l’important ce n’est pas le code mais ce qu’il permet de montrer). C’est mon cas, l’informatique n’est absolument pas mon domaine, juste un outil dont je me sert pour une partie de mon boulot (simulations numériques, affichages prouvables, visualisations, traitement de données). Je n’ai pas le temps de débugger, d’avoir des erreurs à l’exécution (ou du code lent de par les vérifications à l’exécution), et mon code ne sera lu ou utilisé que par un nombre nul ou très très réduit de gens, ce qui compte c’est de vérifier les idées.