IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Caml Discussion :

Question sur les types fantômes et la compilation


Sujet :

Caml

  1. #1
    Membre éclairé
    Avatar de GnuVince
    Profil pro
    Développeur informatique
    Inscrit en
    Avril 2004
    Messages
    679
    Détails du profil
    Informations personnelles :
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Avril 2004
    Messages : 679
    Points : 803
    Points
    803
    Par défaut Question sur les types fantômes et la compilation
    Je voulais jouer avec les types fantômes, mais je crois mal connaître le fonctionnement des modules dans OCaml, car le code suivant compile sans problème:

    SafeString.mli:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    module SafeString : sig
      type 'a t
      type safe
      type unsafe
     
      val new_string : string -> unsafe t
      val unsafe_to_safe : unsafe t -> safe t
      val exec_sql : safe t -> unit
    end

    SafeString.ml

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
     
    module SafeString =
    struct
      type 'a t = Str of string
      type safe
      type unsafe
     
      let new_string s = Str s
     
      let unsafe_to_safe unsafe_str =
        match unsafe_str with
          | Str s -> Str ("[SAFE] " ^ s)
     
      let exec_sql s =
        match s with
          | Str s -> Printf.printf "select * from tbl where id=%s\n" s
    end
     
     
    let _ =
      let s = SafeString.new_string "'world" in
        SafeString.exec_sql s

    Compilation/exécution:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    19:49 vince@archvince:~/prog/ocaml/phantom$ ocamlopt SafeString.mli
    19:49 vince@archvince:~/prog/ocaml/phantom$ ocamlopt SafeString.ml
    19:49 vince@archvince:~/prog/ocaml/phantom$ ./a.out 
    select * from tbl where id='world
    Je me serais attendu à ce que la compilation de SafeString.ml échoue, car j'essaye de passer un unsafe SafeString.t à une fonction qui doit recevoir un safe SafeString.t.

    Est-ce que quelqu'un pourrait me démêler S.V.P.?

    Merci,

    Vincent.

  2. #2
    Membre éprouvé
    Profil pro
    Inscrit en
    Avril 2007
    Messages
    832
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2007
    Messages : 832
    Points : 1 104
    Points
    1 104
    Par défaut
    Les types deviennent fantômes au moment où tu "scelles" le module en cachant la définition des types avec une interface. Dans ton code c'est fait dans le .mli, donc à l'extérieur du module. Seul le code utilisant le module SafeString *de l'extérieur* est concerné. Si tu plaçais ton code d'exemple dans un autre fichier, utilisant le module SafeString.SafeString (le module SafeString dans le fichier "modulisé" safestring.ml), tu aurais une erreur.

    L'autre solution, plus simple et plus logique, est de contraindre directement l'interface de ton module SafeString :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    module type SAFESTRING = sig
      type 'a t
      type safe
      type unsafe
     
      val new_string : string -> unsafe t
      val unsafe_to_safe : unsafe t -> safe t
      val exec_sql : safe t -> unit
    end
     
    module SafeString : SAFESTRING =
    struct
      type 'a t = Str of string
      type safe
      type unsafe
     
      let new_string s = Str s
     
      let unsafe_to_safe unsafe_str =
        match unsafe_str with
          | Str s -> Str ("[SAFE] " ^ s)
     
      let exec_sql s =
        match s with
          | Str s -> Printf.printf "select * from tbl where id=%s\n" s
    end
     
     
    let _ =
      let s = SafeString.new_string "'world" in
        SafeString.exec_sql s
    PS : qu'est-ce que tu essaies d'obtenir au final ? On dirait une interface pour gérer une fonction "escape" dans un contexte web sale, mais je ne comprends pas trop la signification de "exec_sql" du coup.

  3. #3
    Membre éprouvé
    Profil pro
    Inscrit en
    Mars 2010
    Messages
    309
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2010
    Messages : 309
    Points : 928
    Points
    928
    Par défaut
    @bluestorm : bah la signature d'exec_sql attend une chaine qui a été échappée

    Sinon j'ajouterai que tu n'as pas besoin de créer un nouveau type. Tu peux très bien utiliser directement le type string :

    type 'a t = string

  4. #4
    Membre éclairé
    Avatar de GnuVince
    Profil pro
    Développeur informatique
    Inscrit en
    Avril 2004
    Messages
    679
    Détails du profil
    Informations personnelles :
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Avril 2004
    Messages : 679
    Points : 803
    Points
    803
    Par défaut
    @bluestorm: en mettant la fonction d'appel dans un fichier main.ml, j'ai le comportement que je voulais, merci. L'exemple n'a pas d'utilité pratique, c'était juste pour "simuler" le cas où un programmeur oublierait de bien sanitiser une chaîne avant de l'inclure dans un appel SQL.

    @TropMDR: merci pour le petit truc.

  5. #5
    Membre régulier
    Profil pro
    Inscrit en
    Septembre 2007
    Messages
    99
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2007
    Messages : 99
    Points : 93
    Points
    93
    Par défaut
    Citation Envoyé par GnuVince Voir le message
    Je voulais jouer avec les types fantômes, mais je crois mal connaître le fonctionnement des modules dans OCaml, car le code suivant compile sans problème:

    SafeString.mli:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    module SafeString : sig
      type 'a t
      type safe
      type unsafe
     
      val new_string : string -> unsafe t
      val unsafe_to_safe : unsafe t -> safe t
      val exec_sql : safe t -> unit
    end

    SafeString.ml

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
     
    module SafeString =
    struct
      type 'a t = Str of string
      type safe
      type unsafe
     
      let new_string s = Str s
     
      let unsafe_to_safe unsafe_str =
        match unsafe_str with
          | Str s -> Str ("[SAFE] " ^ s)
     
      let exec_sql s =
        match s with
          | Str s -> Printf.printf "select * from tbl where id=%s\n" s
    end
     
     
    let _ =
      let s = SafeString.new_string "'world" in
        SafeString.exec_sql s
    Mais quel code choquant. type 'a t défini par Str of string
    ya pas le moindre 'a dedans. Ça sent le design foireux à 1000 mètres ce truc là.

  6. #6
    Membre éprouvé
    Profil pro
    Inscrit en
    Avril 2007
    Messages
    832
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2007
    Messages : 832
    Points : 1 104
    Points
    1 104
    Par défaut
    C'est justement le principe des types fantômes : on utilise des variables de type qui ne décrivent pas forcément des données physiquement présentes dans les valeurs, mais qui transportent de l'information statique qui nous intéresse. En utilisant un module privé, on peut contrôler la façon dont sont créés et utilisés les éléments de type ('a t), pour garantir certains invariants sur ('a).

    Ici, on garantit que la variable ('a) peut prendre deux valeurs, (safe) et (unsafe), qu'une chaîne fournie donne une valeur (unsafe), et que la seule manière de construire une valeur (safe) est d'utiliser la fonction de protection (unsafe_to_safe). En d'autres termes, on garantit statiquement que toutes les chaînes fournies à exec_sql ont été protégées.

    Si tu remplaces ce squelette par une vraie fonction de protection, tu peux avoir un module qui fait que le compilateur vérifie tout seul l'absence de faille par injection SQL (par exemple).

    Tu devrais prendre un peu de temps pour te familiariser avec les types fantômes, c'est une notion assez utile. Voici deux billets de blog qui en présentent des usages en Caml :
    - Camltastic : Phanton types
    - Static access control using phantom types

  7. #7
    Membre régulier
    Profil pro
    Inscrit en
    Septembre 2007
    Messages
    99
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2007
    Messages : 99
    Points : 93
    Points
    93
    Par défaut
    Parceque
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    module SafeString : sig
      type safe
      type unsafe
     
      val new_string : string -> unsafe
      val unsafe_to_safe : unsafe -> safe
      val exec_sql : safe -> unit
    end
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    module SafeString =
    struct
      type safe = string
      type unsafe = string
     
      let unsafe_to_safe unsafe_str =
        "[SAFE] " ^ unsafe_str
     
      let exec_sql s =
        Printf.printf "select * from tbl where id=%s\n" s
    end
    tu n'aimes pas ça ? Pourtant ça revient exactement au même sans le 'a t.

    [[troll]]
    Voilà ce que c'est de travailler avec un langage sans Monades. Vous passez votre temps à en définir des sous version bcp moins performantes.
    [[/troll]]

  8. #8
    Membre éprouvé
    Profil pro
    Inscrit en
    Mars 2010
    Messages
    309
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2010
    Messages : 309
    Points : 928
    Points
    928
    Par défaut
    Citation Envoyé par NokyDaOne Voir le message
    tu n'aimes pas ça ? Pourtant ça revient exactement au même sans le 'a t.
    Supposons maintenant que tu veuilles définir la concaténation de deux chaines ? En supposant que deux chaines "safe" concaténées restent safes, avec les types fantomes, tu peux écrire
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    let concat s1 s2 = s1 ^s2
    et lui donner le type
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    concat : 'a t -> 'a t -> 'a t
    Dans ta version, il te faudra concat_safe et concat_unsafe, puisque tu as perdu le polymorphisme

    Citation Envoyé par NokyDaOne Voir le message
    [[troll]]
    Voilà ce que c'est de travailler avec un langage sans Monades. Vous passez votre temps à en définir des sous version bcp moins performantes.
    [[/troll]]
    Un "langage sans monade" ? Mince alors, comment ils font pour écrire lwt ? Qu'est ce que le type option ?

    Ce n'est pas parce qu'il n'y a pas de type classes, et donc pas de possibilité de surcharger >>= qu'il n'y a pas de monades.

    C'est bien de vouloir troller, c'est mieux quand il y a vaguement quelque chose derrière.

  9. #9
    Membre régulier
    Profil pro
    Inscrit en
    Septembre 2007
    Messages
    99
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2007
    Messages : 99
    Points : 93
    Points
    93
    Par défaut
    Citation Envoyé par TropMDR Voir le message
    Supposons maintenant que tu veuilles définir la concaténation de deux chaines ? En supposant que deux chaines "safe" concaténées restent safes, avec les types fantomes, tu peux écrire
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    let concat s1 s2 = s1 ^s2
    et lui donner le type
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    concat : 'a t -> 'a t -> 'a t
    Dans ta version, il te faudra concat_safe et concat_unsafe, puisque tu as perdu le polymorphisme
    C'est pas faux. Mais je suis sûr que l'on peut faire sans le type fantôme, j'y réfléchirai ce soir.


    Un "langage sans monade" ? Mince alors, comment ils font pour écrire lwt ? Qu'est ce que le type option ?

    Ce n'est pas parce qu'il n'y a pas de type classes, et donc pas de possibilité de surcharger >>= qu'il n'y a pas de monades.

    C'est bien de vouloir troller, c'est mieux quand il y a vaguement quelque chose derrière.
    Euh normalement on ne nourrit pas un troll.

  10. #10
    Membre éprouvé
    Profil pro
    Inscrit en
    Avril 2007
    Messages
    832
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2007
    Messages : 832
    Points : 1 104
    Points
    1 104
    Par défaut
    Un autre exemple de code qui profite des types fantômes :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    module Expr : sig
      type 'a expr
      val int : int -> int expr
      val bool : bool -> bool expr
      val sum : int expr -> int expr -> int expr
      val if_ : bool expr -> 'a expr -> 'a expr -> 'a expr
    end = struct
      type t =
        | Int of int
        | Bool of bool
        | Sum of t * t
        | If of t * t * t
      type 'a expr = t
     
      let int n = Int n
      let bool b = Bool b
      let sum a b = Sum (a, b)
      let if_ p a b = If (p, a, b)
    end
    Il faut noter que les signatures apportent ici une sûreté de type à la construction des valeurs. Même si l'on donnait une façon d'inspecter les valeurs de type "expr" (ce qui est possible), certaines opérations naturelles dans ce modèle (par exemple (eval : 'a expr -> 'a)) ne sont pas faciles à coder dans le cadre du système de type de OCaml, et demandent des choses plus avancées comme les GADT, ou un usage prudent de Obj.magic.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Débutante en Haskell, question sur les types.
    Par avator dans le forum Haskell
    Réponses: 6
    Dernier message: 22/09/2008, 02h24
  2. Réponses: 2
    Dernier message: 28/03/2008, 23h28
  3. petite question sur les types de champs
    Par charlie koller dans le forum Débuter
    Réponses: 2
    Dernier message: 21/02/2007, 17h57
  4. Questions sur les types énumérés
    Par Premium dans le forum Langage
    Réponses: 5
    Dernier message: 12/11/2006, 18h00
  5. [SQL 2000] Question sur les types de données
    Par Angath dans le forum MS SQL Server
    Réponses: 4
    Dernier message: 03/11/2006, 14h05

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo