Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

modules_récursifs #8187

Closed
vicuna opened this issue Jun 25, 2003 · 5 comments
Closed

modules_récursifs #8187

vicuna opened this issue Jun 25, 2003 · 5 comments
Labels

Comments

@vicuna
Copy link

vicuna commented Jun 25, 2003

Original bug ID: 1730
Reporter: administrator
Status: closed
Resolution: fixed
Priority: normal
Severity: minor
Category: ~DO NOT USE (was: OCaml general)

Bug description

Bonjour,

après avoir testé les modules récursifs, je suis tombés sur un bug et
quelques limitations :

  • type checker crash :

    cafe[15:21]%cat tmp.mli
    module rec M1 : sig
    class o : object end
    end
    and M2 : sig
    val f : #M1.o -> bool
    end
    cafe[15:21]%ocamlc tmp.mli
    Fatal error: exception Assert_failure("typing/typetexp.ml", 275, 10)

  • les modules récursifs ne se marient pas bien avec les variantes
    polymorphes :

    module rec M1 : sig
    type t = [ A | B ]
    end
    and M2 : sig
    val f : [> M1.t ] -> bool
    type t = [ M1.t | `C ]
    end

  • une autre limitation est l'impossibilité de définir des signatures
    récursives. Est-ce une limitation technique, ou un oubli ?

Enfin un voeux pour l'avenir : les modules récursifs appliqués à un seul
module permettent de contourner des limitations du système de type
actuel :

  • classe et type mutuellements récursifs (mais on ne peut pas définir la
    signature correspondante !),
  • récursion polymorphe,
  • coercion de self dans une classe en cours de définition,
  • d'autres ?
    Je me demandait donc si en cas de présence d'un fichier .mli, le
    compilateur pourrait comprendre le module courant comme récursif pour
    éviter une encapsulation supplémentaire, en réservant alors un mot clé
    (Rec ? Self ?) pour désigner le module.
@vicuna
Copy link
Author

vicuna commented Jun 26, 2003

Comment author: administrator

Bonjour,

  • type checker crash :

    cafe[15:21]%cat tmp.mli
    module rec M1 : sig
    class o : object end
    end
    and M2 : sig
    val f : #M1.o -> bool
    end
    cafe[15:21]%ocamlc tmp.mli
    Fatal error: exception Assert_failure("typing/typetexp.ml", 275, 10)

C'est corrigé. Merci de l'avoir signalé.

  • les modules récursifs ne se marient pas bien avec les variantes
    polymorphes :

    module rec M1 : sig
    type t = [ A | B ]
    end
    and M2 : sig
    val f : [> M1.t ] -> bool
    type t = [ M1.t | `C ]
    end

Ce n'est pas vraiment spécifique aux variantes: il y a des limitations
comparables avec les module types, e.g.

    module rec M1 : sig module type U = sig val x:int end end
    and M2 : sig include M1.U end

Dans les deux cas, on essaye d'utiliser "trop tôt" une composante des
définitions mutuellement récursives. Ça peut cacher des récursions
mal fondées au niveau des types, e.g.

  module rec M1 : sig
    type t = [ M2.t | `A]
  end
  and M2 : sig
    type t = [ M1.t | `C] 
  end
  • une autre limitation est l'impossibilité de définir des signatures
    récursives. Est-ce une limitation technique, ou un oubli ?

On peut donner des signatures (récursives) à des modules récursifs,
comme le montre votre exemple ci-dessus. Mais il est vrai uq'on ne
peut pas donner de nom (par une liaison "module type") à ces
signatures. La raison est simple: ces signatures dépendent de noms de
modules liés par la définition "module rec", et donc on ne peut pas
les nommer par "module type" avant le "module rec":

module type SIGM2 = sig val f : #M1.o -> bool end

"M1" ne ferait référence à rien.

Enfin un voeux pour l'avenir : les modules récursifs appliqués à un seul
module permettent de contourner des limitations du système de type
actuel :

  • classe et type mutuellements récursifs (mais on ne peut pas définir la
    signature correspondante !),
  • récursion polymorphe,
  • coercion de self dans une classe en cours de définition,
  • d'autres ?
    Je me demandait donc si en cas de présence d'un fichier .mli, le
    compilateur pourrait comprendre le module courant comme récursif pour
    éviter une encapsulation supplémentaire, en réservant alors un mot clé
    (Rec ? Self ?) pour désigner le module.

Le typage et la compilation d'un module récursif sont assez différents
de ceux d'un module / d'une unité de compilation classique. En
particulier, il y a de fortes restrictions qui pèsent sur les modules
récursifs. Je ne crois pas que cela soit faisable (actuellement) de
traiter toutes les unités de compilation comme des "module rec".
Il est plus raisonnable de faire des sous-modules récursifs dans une
unité de compilation lorsqu'on en a besoin.

Très cordialement,

  • Xavier Leroy

@vicuna
Copy link
Author

vicuna commented Jun 26, 2003

Comment author: administrator

Fixed 2003-06-26 by XL.

@vicuna vicuna closed this as completed Jun 26, 2003
@vicuna
Copy link
Author

vicuna commented Jun 26, 2003

Comment author: administrator

On Thu, 26 Jun 2003, Jacques Garrigue wrote:

Surtout qu'il existe d'autres solutions plus simples (et plus sûres)
dans beaucoup de cas:

Ces solution sont peut être plus simple du point de vue du compilateur,
mais pas du programmeur !

  • classe et type mutuellements récursifs (mais on ne peut pas définir la
    signature correspondante !),
    La solution par paramètrisation du type est un FAQ. Elle ne pose aucun
    problème à ma connaissance (autre que d'ajouter des paramètres dans le
    code)

Je trouve ce code beaucoup plus clair que le même avec la paramétrisation

module rec M
: sig
type t = A of int | B of M.c
class c : object method m : t -> int end
end
= struct
type t = A of int | B of M.c
class c = object
method m = function
| A i -> i
| B _ -> 0
end
end

  • récursion polymorphe,
    Ça peut se faire avec un record à champ polymorphe.

Dans le cas des classes, je ne vois pas comment contourner :
(même si ce code est peu util, puisque ne passe pas bien à l'héritage)

module rec M :
sig
class ['a] c : 'a -> object
method map : ('a -> 'b) -> 'b M.c
end
end
= struct
class ['a] c (x : 'a) = object
method map : 'b. ('a -> 'b) -> 'b M.c
= fun f -> new M.c (f x)
end
end

@vicuna
Copy link
Author

vicuna commented Jun 26, 2003

Comment author: administrator

From: lvibert@irisa.fr

Je n'avais pas testé les modules récursifs jusqu'à présent, donc je
débarque un peu.

après avoir testé les modules récursifs, je suis tombés sur un bug et
quelques limitations :
[..]

  • les modules récursifs ne se marient pas bien avec les variantes
    polymorphes :

    module rec M1 : sig
    type t = [ A | B ]
    end
    and M2 : sig
    val f : [> M1.t ] -> bool
    type t = [ M1.t | `C ]
    end

En effet, pour les types de classes et de variants il faudrait la
signature de M1, même partielle, soit disponible dans M2. Je ne sais
pas si c'est possible, mais c'est une limitation gênante.

À noter que
type t = [ A | B ] and t' = [t | `C]
n'est pas accepté par le typeur, mais
class type c1 = object method x : int end
and c2 = object inherit c1 method y : int end
l'est.

Enfin un voeux pour l'avenir : les modules récursifs appliqués à un seul
module permettent de contourner des limitations du système de type
actuel :

  • classe et type mutuellements récursifs (mais on ne peut pas définir la
    signature correspondante !),
  • récursion polymorphe,
  • coercion de self dans une classe en cours de définition,
  • d'autres ?
    Je me demandait donc si en cas de présence d'un fichier .mli, le
    compilateur pourrait comprendre le module courant comme récursif pour
    éviter une encapsulation supplémentaire, en réservant alors un mot clé
    (Rec ? Self ?) pour désigner le module.

Il me semble que les modules recursifs répondent à un problème
précis et apparu souvent: récursion entre un type et un foncteur
(usage récursif de Set par exemple). Je ne suis pas sûr que ce soit
une bonne idée de les vendre pour plus que ça.
Surtout qu'il existe d'autres solutions plus simples (et plus sûres)
dans beaucoup de cas:

  • classe et type mutuellements récursifs (mais on ne peut pas définir la
    signature correspondante !),
    La solution par paramètrisation du type est un FAQ. Elle ne pose aucun
    problème à ma connaissance (autre que d'ajouter des paramètres dans le
    code)
  • récursion polymorphe,
    Ça peut se faire avec un record à champ polymorphe.

type f = {f:'a. 'a -> unit}
let rec f0 = {f = fun _ -> f0.f 1; f0.f true}
let f = f0.f

Et ça pourrait se faire plus directement dans le core-language si on
se mettait d'accord sur une syntaxe.

  • coercion de self dans une classe en cours de définition,
    Il suffit de définir le type vers lequel on veut coercer à l'avance.
    Les modules récursifs n'aident en rien là-dedans.
    Un cas particulier est prévu pour l'auto-coercion:

class c = object (self) method c = (self :> c) end

Donc il ne me semble pas gênant que les modules récursifs aient un
certain nombre de limitations.

Quand à comprendre le module courant comme récursif dans les .mli, ça
demande impérativement qu'on puisse vérifier que l'implémentation ne
va pas partir en boucle infinie.

À noter que les seuls avantages des modules sur les records à champs
polymorphes sont la générativité (capacité à créer des nouveaux types
distincts) et la subsomption (capacité à oublier des champs).
(Par rapport aux classes, à nouveau la générativité, et une
implémentation plus efficace.)
Toutes choses fort utiles, mais dont on n'a pas systématiquement
besoin dans tous les cas.

   Jacques

@vicuna
Copy link
Author

vicuna commented Jun 26, 2003

Comment author: administrator

From: Laurent Vibert lvibert@irisa.fr

Surtout qu'il existe d'autres solutions plus simples (et plus sûres)
dans beaucoup de cas:

Ces solution sont peut être plus simple du point de vue du compilateur,
mais pas du programmeur !

Ah bon. Pourtant il me semble qu'on écrit exactement les mêmes
informations, et même parfois moins.
Mais je reconnais que les modules ont un avantage: leur signature
réutilise la même syntaxe que pour les .mli.
Et je n'ai aucune intention de polémiquer: chacun ses goûts.

  • classe et type mutuellements récursifs (mais on ne peut pas définir la
    signature correspondante !),
    La solution par paramètrisation du type est un FAQ. Elle ne pose aucun
    problème à ma connaissance (autre que d'ajouter des paramètres dans le
    code)

Je trouve ce code beaucoup plus clair que le même avec la paramétrisation

module rec M
: sig
type t = A of int | B of M.c
class c : object method m : t -> int end
end
= struct
type t = A of int | B of M.c
class c = object
method m = function
| A i -> i
| B _ -> 0
end
end

type 'c t0 = A of int | B of 'c
class c = object
method m : c t0 -> int = function
| A i -> i
| B _ -> 0
end
type t = c t0

C'est plus court. Mais peut-être pas très intuitif, je l'admets. C'est
pour ça que c'est un FAQ.

Et si il est permis d'écrire explicitement le type de la classe, on
peut écrire:

type t = A of int | B of c_t
and c_t = < m : t -> int >
class c = object
method m = function
| A i -> i
| B _ -> 0
end

En fait je crois que le découplage des classes et type de classes est
un objectif sain, comme ça on pourrait écrire:

type t = A of int | B of c_t
and c = object method m : t -> int end
class c : c = object
method m = function
| A i -> i
| B _ -> 0
end

Plus propre que les alternatives, non?

  • récursion polymorphe,
    Ça peut se faire avec un record à champ polymorphe.

Dans le cas des classes, je ne vois pas comment contourner :
(même si ce code est peu util, puisque ne passe pas bien à l'héritage)

module rec M :
sig
class ['a] c : 'a -> object
method map : ('a -> 'b) -> 'b M.c
end
end
= struct
class ['a] c (x : 'a) = object
method map : 'b. ('a -> 'b) -> 'b M.c
= fun f -> new M.c (f x)
end
end

Tel quel ton exemple ne peut pas marcher: il ne peut pas y avoir de
récursion polymorphe en l'absence d'un type construit
(somme ou record).

let c = new M.c 1;;

val c : int M.c =

c#map;;

  • : (int -> '_a) -> int M.c =

Je sens un invariant cassé dans les modules récursifs...
Bienvenue au club, Xavier!

type 'a c0 = C of < map : 'b. ('a -> 'b) -> 'b c0 >

type m = {mutable new_c : 'a. 'a -> 'a c0}
let m = {new_c = fun _ -> failwith "new_c"}

class ['a] c (x : 'a) = object
method map : 'b. ('a -> 'b) -> 'b c0 = fun f -> m.new_c (f x)
end
let () = m.new_c <- fun x -> C (new c x)

C'est pas beau...
Là je reconnais que la version avec modules récursifs est plus jolie:
module rec M : sig
class ['a] c : 'a -> object
method map : ('a -> 'b) -> 'b M.c_t
end
type 'a c_t = C of 'a c
end = struct
class ['a] c (x : 'a) = object (_ : _ #M.c)
method map = fun f -> M.C (new M.c (f x))
end
type 'a c_t = C of 'a c
end

    Jacques

@vicuna vicuna added the bug label Mar 19, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant