Dans une application de gestion (au sens large) qui traite nombre informations, on peut facilement se retrouver avec moult variables textuelles voyageant dans le code au gré des traitements.
Le risque arrive vite d'avoir pléthores de fonctions prenant des chaînes en argument. Évidemment une chaîne étant équivalente à une autre, les fautes d'étourderies et autres valeurs mal traitées traitées (inversions, oublis), impliquent assez vite des erreurs survenant à l'exécution.
En paradigme objet, on peut s'amuser à créer un objet par champ, ce qui peut être lourd et non adapté car les ORMs sont tentent d'insérer une logique objet non adaptée à la logique relationnelle. Les ORMs courrament utilisés implique qu'une ligne de la base soit équivalent à une instance, mais que se passe t-il lorsque qu'une fonction traite divers champs de divers table, ou encore qu'on ait besoin d'une requête un peu complexe pour récupérer certaines valeurs non associées à des objets ?
En paradigme fonctionnel, on a la possibilité d'utiliser les types fantômes, correspondant à un champ de la base de donnée.
Ces types permettent de faire croire que deux éléments, tous deux d'un type de base ( genre string ou int) sont de types différents.
L'exemple explicatif sera en ocaml (un haskelien aura la bonté de nous le traduire, je n'en doutes pas).
Les types fantômes en OCaml
(*signature du module, ou encore définition des prototypes comme une interface en java*)
module T : sig
(*on défini nos types spécialisés*)
type prenom
type nom
(*on devra passer par ces fonctions pour créer les chaines typées. *)
val makeNom : string -> nom
val makePrenom : string -> prenom
val fromNom : nom -> string
val fromPrenom : prenom -> string
end = struct
(*Nous sommes dans l'implémentation du module, on défini que nos types particuliers sont des chaînes*)
type nom = string
type prenom = string
(*la définition de type dans la signature nous garantie que l'on renvoi bien un type T, alors que l'implémentation est une bête fonction identité*)
let makeNom s = s
let makePrenom s = s
let fromPrenom s = s
let fromNom s = s
end;;
On va maintenant constater que bien que l'on construit deux chaines, les fonctions makeNom et makePrenom étant codées comme des fonctions identités, on obtient deux types différents
let a = T.makeNom ("1 nom");;
let b = T.makePrenom ("1 prenom");;
b = a;;
Le compilateur nous indique qu'il y a erreur de type, en effet on compare deux types différends :
line 1, characters 4-5:
Error: This expression has type T.nom but an expression was expected of type T.prenom
L'intérêt de cet outil de typage est d'avoir la garantie que l'on ne va pas se tromper de champ, ainsi, dans un découpage Modèle/Vue/Controlleur, toutes les fonctions du modèles vont traiter un type correspondant à chaque champ de la base de donnée :
(* Soit le type :*)
type client = { prenom : T.prenom ; nom : T.nom ; id : int};;
let chercheClientByPrenom prenom nom clients =
List.find (fun cli -> cli.prenom = prenom || cli.nom = nom) clients
que Ocaml détecte comme une fonction de type :
val chercheClientByPrenom : T.prenom -> T.nom -> client list -> client = <fun>
Conclusion
En obligeant le développeur à n'utiliser, par construction, à la sortie d'un espèce d'ORM ou de simples requêtes SQL, ces types fantômes, on garantie que ce sont bien les bonnes donnés qui seront traités à la bonne place, car le compilateur refusera de compiler s'il y a une erreur.
Il existe d'autre manière de profiter des possibilités de typage de cette race de langage fonctionnel, en particulier en utilisant un type somme, mais je trouve cette manière plus propre, car on regroupe toutes les définitions de "choses" (un nom, un prénom, un numéro de facture) dans un seul module, dédié à cela.
J'avoue ne pas avoir d'idée de la manière avec laquelle on pourrait implémenter ce genre de chose dans des langages objets plus classique. Mon expérience m'a appris que l'utilisation d'ORM automatique classique ne suffit pas à garantir l'absence de problèmes.
# Comme Hibernate ne savait pas que c'était impossible, ils l'ont fait.
Posté par Sufflope (site web personnel) . Évalué à 8.
(oui ça map sur un objet m'enfin c'est de l'OO hein… je suppose que tu voulais dire qu'Hibernate ne map que sur des entités)
[^] # Re: Comme Hibernate ne savait pas que c'était impossible, ils l'ont fait.
Posté par mackwic . Évalué à -4.
Ce qui est bien, c'est qu'avec des exemples comme ca on est convaincu que l'OCaml est super lisible comparé au C++
[^] # Re: Comme Hibernate ne savait pas que c'était impossible, ils l'ont fait.
Posté par jtremesay (site web personnel) . Évalué à 8.
Sauf que là c'est du java.
[^] # Re: Comme Hibernate ne savait pas que c'était impossible, ils l'ont fait.
Posté par mackwic . Évalué à 1.
Honte à moi ! /o\
[^] # Re: Comme Hibernate ne savait pas que c'était impossible, ils l'ont fait.
Posté par Ontologia (site web personnel) . Évalué à 0.
Autant C++ à force de sédimentation permet de l'exprimer (voir plus bas), autant la sémantique de Java est absolument pas faite pour ça. D'où ce genre de hack.
« Il n’y a pas de choix démocratiques contre les Traités européens » - Jean-Claude Junker
[^] # Re: Comme Hibernate ne savait pas que c'était impossible, ils l'ont fait.
Posté par Sufflope (site web personnel) . Évalué à 6.
Je comprends pas pourquoi c'est un hack. Avec un usage normal d'Hibernate, il trouve tout seul quelle classe (entité) construire ; si tu veux pas reconstruire une entité, y a qu'à lui dire quoi construire, comme dans mon exemple.
Ensuite pour ton envie qu'une comparaison de Nom et Prénom ne compile pas, bah tu t'écris tes deux classes qui encapsulent une String, tu implémentes equals, compareTo si t'es chaud, et puis ça fera pareil (enfin pas avec "==" évidemment, puisque comme tu le sais c'est une comparaison de références et pas de valeur ; tu n'oserais pas troller sur Java sans rien savoir dessus, évidemment).
[^] # Re: Comme Hibernate ne savait pas que c'était impossible, ils l'ont fait.
Posté par Batchyx . Évalué à 3.
Et si tu à 10 types de String différent, tu t'implémente 10 classes ? Parce que là on parle justement de pouvoir faire ça sans devoir écrire trop de code.
Ou bien le langage le supporte facilement, ou bien il faut pouvoir faire de la méta-programmation. Et j'aimerai bien voir comment s'en sort la méta-programmation de Java, donc certains se targuent qu'elle est supérieure à C++…
[^] # Re: Comme Hibernate ne savait pas que c'était impossible, ils l'ont fait.
Posté par Sufflope (site web personnel) . Évalué à 2.
Je dois pas m'y connaître assez mais pour moi type est à peu près égal à classe (du moins en POO) donc oui… de la même façon que dans le journal y a un type défini par type de String, et deux méthodes from/to à chaque fois aussi…
Après si ta seule problématique est de faire de la discrimination sur des String, à la volée je testerais un truc comme ça :
Après si t'as la flemme d'écrire quelques classes pour implémenter ta logique métier fallait choisir autre chose que la programmation.
[^] # Re: Comme Hibernate ne savait pas que c'était impossible, ils l'ont fait.
Posté par navaati . Évalué à 5.
Houlla non, ce code ferait une vérification à l'exécution alors que ce dont on parle dans ce Nal c'est de vérification statique, à la compile.
[^] # Re: Comme Hibernate ne savait pas que c'était impossible, ils l'ont fait.
Posté par YBoy360 (site web personnel) . Évalué à 2.
en étant un peu bourain et en utilisant JDT tu peux sans doute ajouter une annotation pour créer un type fantôme à partir du nom de l'attribut, qui fait que l'ide puisse faire ce genre de check au moment ou tu tappes le code.
[^] # Re: Comme Hibernate ne savait pas que c'était impossible, ils l'ont fait.
Posté par Thomas Douillard . Évalué à 6.
C'est un raisonnement antinomique avec le principe de fainéantise efficace qui devrait être le fondement de tout programmeur.
[^] # Re: Comme Hibernate ne savait pas que c'était impossible, ils l'ont fait.
Posté par lendemain . Évalué à 1.
Je ne me souviens plus bien de c++ et java, mais la méta-programmation en c++ c'est à la compilation alors que celle de java c'est de la réléxivité qui elle est à l'éxécution.
[^] # Re: Comme Hibernate ne savait pas que c'était impossible, ils l'ont fait.
Posté par Milridor (site web personnel) . Évalué à 1.
Les generics sont évalués à la compilation…
# Haskell newbie
Posté par Zylabon . Évalué à 1. Dernière modification le 25 août 2012 à 18:12.
C'est comme ça ?
Les vrais haskeliens n'hésitez pas à corriger.
(ya un petit bug dans la coloration syntaxique, haskell supporte très bien le code en utf-8)
Please do not feed the trolls
[^] # Re: Haskell newbie
Posté par stopspam . Évalué à 2.
Des accents dans le nom de variables ?
[^] # Re: Haskell newbie
Posté par Zylabon . Évalué à 1.
Oui, c'est c'est pas du vrai code. Cela dit, pour les lettres grecs c'est dommage de jamais en mettre. Combien de fois dans du code j'ai vu des Lambda écrit en toutes lettres alors que Λ serait bien plus facile à lire. Enfin bref.
Sinon, pour une jolie interface, on la ferait comme ça ?
Je dois réécrire 15 fois chaque lignes pour que ça marche, je crois que c'est le langage le plus capricieux que j'ai croisé (et les messages d'erreurs de ghc n'aident pas beaucoup).
Please do not feed the trolls
[^] # Re: Haskell newbie
Posté par Sufflope (site web personnel) . Évalué à 4.
Bah en Haskell j'en sais rien mais en Java par exemple, les identifiants sont en Unicode, donc à part quelques règles (du style ne pas commencer un nom de variable par un chiffre) tu peux utiliser à peu près tout ce que tu veux… c'est un peu chercher la merde mais tu peux.
[^] # Re: Haskell newbie
Posté par stopspam . Évalué à 3.
C'était le sens de ma remarque plus haut.
[^] # Re: Haskell newbie
Posté par MsieurHappy . Évalué à 4.
Le type fantôme peut être avantageusement déclaré via
newtype
:On peut ensuite aussi jouer avec l'extension
OverloadedStrings
comme suit:Du coup on a plus du tout besoin d'utiliser de constructeur.
Mais on peut toujours mettre de façon explicite le type de la chaîne.
ne compileront pas.
# En C++...
Posté par small_duck (site web personnel) . Évalué à 9.
Je suis fan de types fantômes, et j'en colle partout (trop?). Mais quand je fais du C++ en lieu et place d'OCaml, je m'assure de toujours avoir dans un coin ma template StrongId:
Il est ensuite possible d'ajouter des méthodes supplémentaires pour comparer, sérialiser, afficher… Voire de rajouter un peu de template magic pour permettre de diviser une distance par un temps et d'obtenir une vitesse.
[^] # Re: En C++...
Posté par Ontologia (site web personnel) . Évalué à 2.
Ce qui est marrant dans cet exemple, c'est que ce n'est pas le compilateur qui détecte l'erreur de type, mais c'est le fait que le compilateur exécute le code liés aux templates qui va le faire déboucher sur une erreur d'exécution lors de la phase de compilation.
« Il n’y a pas de choix démocratiques contre les Traités européens » - Jean-Claude Junker
[^] # Re: En C++...
Posté par Batchyx . Évalué à 5.
Sauf que dans ce cas là, c'est une vraie erreur de type.
Tu à un objet de type
StrongId<DistanceDiscriminant, int>
à comparer avec unStrongId<TempsDiscriminant, int>
, or la substitution donnebool StrongId<DistanceDiscriminant, int>::operator==(const StrongId<DistanceDiscriminant, int> & other)
(ça manquerai d'unconst
d'ailleurs)Tu à donc une méthode qui prend un
const StrongId<DistanceDiscriminant, int> &
auquel tu essaye de faire passer unStrongId<TempsDiscriminant, int>&
et le compilateur ne trouve pas de conversion implicite possible. Ce n'est pas différent de faire passer unint
pour unstd::string
.Ça aurai pu être une erreur d'instanciation si c'était défini comme étant
et que tu voulais comparer un
StrongId<DistanceDiscriminant, int>
et unStrongId<DistanceDiscriminant, std::string>
(même si ça n'a aucun sens).# Mapping Object-Relationnel
Posté par Raoul Volfoni (site web personnel) . Évalué à 4.
Pour les heureux qui ont la chance de ne pas s'être encore cassé les dents sur la problématique dite du Défaut d'impédance il existe un très bon article sur le Wikipédia anglophone:
https://en.wikipedia.org/wiki/Object-relational_impedance_mismatch
Merci pour ton exemple même s'il ne résoud qu'une partie des problèmes.
# Un peu faible
Posté par Perthmâd (site web personnel) . Évalué à 6.
Ça me paraît être une utilisation très simple des types fantômes, et ça ressemble plus à l'insertion d'une dose de typage nominal en utilisant des signatures de modules que des types fantômes full-fledged©.
La manière de faire usuelle est plutôt du genre :
(et éventuellement d'autres fonctions de création.)
Le code est plus générique, et permet en sus des trucs bien sioux. Notez bien par exemple la covariance du type +'a t dans la signature. En la mettant contravariante et à l'aide des variants polymorphes, on peut mimer le sous-typage des records ou des objets. J'ai pas d'exemples simples sous la main, mais la technique est assez employée.
[^] # Re: Un peu faible
Posté par Matthieu Lemerre (site web personnel) . Évalué à 6.
Je dirais même plus: pas besoin de langage fonctionnel pour faire ça, on peut y arriver avec n'importe quel langage fortement typé… Et même en C, en bidouillant un peu pour profiter du fait que les structures en C sont fortement typées:
Les vrais types fantômes sont definis par le fait que ce sont des types paramètres, mais dont le paramètre n'apparait pas dans la définition: http://www.haskell.org/haskellwiki/Phantom_type
http://l-lang.org/ - Conception du langage L
[^] # Re: Un peu faible
Posté par Axioplase ıɥs∀ (site web personnel) . Évalué à 3.
Ta remarque sur le fait qu'on paramétrise le type mais pas l'expression, est celle qui donne tout son sens.
C'est d'autant plus intéressants qu'on s'en sort avec des types inhabités.
Par exemple :
nous donne l'erreur
Couldn't match expected type `Serviettes'
with actual type `Torchons'
alors qu'il n'existe aucune expression de type Serviette ou Torchon.
On a donc statiquement la garantie qu'on ne mélangera pas des serviettes avec des torchons.
[^] # Re: Un peu faible
Posté par Nicolas Boulay (site web personnel) . Évalué à 2.
l'avantage de ocaml est d'avoir à écrire qu'une seul fois les fonction qui manipule les 2 types de la même façon. Je ne suis pas sûr que cela soit possible en C.
Par exemple pour redéfinir l'opérateur concatenation ().
"La première sécurité est la liberté"
[^] # Re: Un peu faible
Posté par Zylabon . Évalué à 1.
On peut n'écrire qu'une seule fois les fonctions,
Please do not feed the trolls
[^] # Re: Un peu faible
Posté par Nicolas Boulay (site web personnel) . Évalué à 3.
Définir les fonctions dans des macros, je n'appelle pas cela de la programmation(mondieukesaimoche).
"La première sécurité est la liberté"
[^] # Re: Un peu faible
Posté par Axioplase ıɥs∀ (site web personnel) . Évalué à 3.
Ben toi, tu fais pas du Lisp !
[^] # Re: Un peu faible
Posté par Ontologia (site web personnel) . Évalué à 2.
Très intéressant !
Questions :
« Il n’y a pas de choix démocratiques contre les Traités européens » - Jean-Claude Junker
[^] # Re: Un peu faible
Posté par gasche . Évalué à 3.
La covariance est là pour permettre la généralisation polymorphe des expressions de la forme
T.cast "foo"
(essaie sans et avec le+
). J'en parle ici sur reddit. La fonction de cast va renvoyer un terme polymorphe de type'a T.t
, à toi de l'utiliser de la façon qui va bien (je suis d'accord sur le fait que, du coup, l'exemple est un peu bizarre).[^] # Re: Un peu faible
Posté par Ontologia (site web personnel) . Évalué à 2. Dernière modification le 03 septembre 2012 à 12:07.
J'ai essayé
Et les deux versions se comportent exactement de la même façon, ne restreignent rien
Je suppose que c'est normal ?
« Il n’y a pas de choix démocratiques contre les Traités européens » - Jean-Claude Junker
[^] # Re: Un peu faible
Posté par gasche . Évalué à 2.
Tu as dû te tromper, chez moi
Foo.inject []
ne donne pas le même résultat dans le premier cas.[^] # Re: Un peu faible
Posté par Ontologia (site web personnel) . Évalué à 2.
C'est très bizarre ! Voici mon transcript :
« Il n’y a pas de choix démocratiques contre les Traités européens » - Jean-Claude Junker
[^] # Re: Un peu faible
Posté par gasche . Évalué à 3.
Tu ne jouais pas au jeu des sept différences quand tu étais petit ?
[^] # Re: Un peu faible
Posté par Ontologia (site web personnel) . Évalué à 2.
Au temps pour moi :-)
N'étant pas dans les petits papiers de Leroy et Garrigues, ça signifie quoi '_a ? Qu c'est "plus" polymorphique ?
« Il n’y a pas de choix démocratiques contre les Traités européens » - Jean-Claude Junker
[^] # Re: Un peu faible
Posté par gasche . Évalué à 3.
'_a
n'est pas une variable polymorphe, c'est une variable d'inférence encore inconnue, mais son premier usage fixera sa valeur (donc le bout de code que j'ai mis, qui l'utilise à deux types différents, va échouer avec une erreur de typage). Pour plus de détails sur pourquoi le+
règle le problème, il faut lire (le début de) l'article sur la "relaxed value restriction".Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.