4 votes

Comment pointer vers un champ d'enregistrement dans le cas de types d'objets extensibles ?

Je réfléchis à un moyen de définir une lentille simple sur un objet dans Reason.

J'essaie d'utiliser des objets extensibles (avec des .. ajouté à la liste des champs) avec le code suivant :

type hasName('a, 't) = {.. name: 't} as 'a;
type lens('s, 'v) = {
  get: 's => 'v,
  set: ('s, 'v) => 's,
};
let nameLens: lens(hasName('a, 't), 't) = {
  get: s => s.name,
  set: (s, v) => {...s, name: v},
}

J'obtiens l'erreur "The record field name can't be found.", bien que le type de hasName devrait définitivement en avoir un... Qu'est-ce que je fais de mal ici ?

Avertissement : je suis vraiment novice en matière de Reason/OCaml, il se peut donc que je rate des choses évidentes.

5voto

glennsl Points 9939

Le message d'erreur n'est pas génial (c'est un euphémisme), mais si vous le lisez attentivement, il indique le problème. Il est écrit "Le champ du dossier name can't be found", ce qui n'est pas si étrange si l'on considère que vous avez un objet et non un enregistrement. Il s'agit d'une erreur parce que les membres d'un objet sont accédés avec # pas . (voir les documents de Reason sur les objets ).

Si je comprends bien, la raison en est qu'OCaml ne supporte pas le polymorphisme ad hoc (c'est-à-dire la surcharge de fonctions ou d'opérateurs) et donc, pour que l'inférence de type fonctionne bien, il faut distinguer syntaxiquement les opérations sur différents types. Je soupçonne également que la raison pour laquelle il ne dit pas simplement "mec, c'est un objet, pas un enregistrement" est que le système de type n'a pas de concept de type d'enregistrement quelconque, mais exige un type d'enregistrement spécifique. Il essaie donc d'abord de trouver le champ afin d'identifier ensuite le type d'enregistrement spécifique qui lui est associé. S'il ne peut pas trouver le champ, ce n'est pas considéré comme une erreur de type car il n'y a pas de type approprié avec lequel entrer en conflit.

Dans tous les cas, le getter est résolu assez facilement en utilisant # au lieu de . . Le setter est toutefois plus problématique. Ici aussi, vous utilisez la syntaxe de mise à jour des enregistrements et vous obtiendrez une erreur similaire, mais malheureusement il n'y a pas d'équivalent direct pour les objets.

Une partie de ce qui rend les objets orientés objet est que leur implémentation est cachée. Vous ne pouvez pas copier un objet de l'extérieur si vous ne savez pas ce qu'il contient. Et si vous pouvez créer un objet différent conforme au même type d'objet, vous ne pouvez le faire que si vous connaissez le type concret. Ce qui dans ce cas n'est pas le cas.

Ainsi, la façon de mettre à jour un objet de façon immuable est de le faire à l'intérieur de l'objet, dans une méthode setter, où vous savez tout ce que vous devez savoir. La syntaxe pour mettre à jour de façon immuable l'objet actuel est la suivante {<name: newName>} qui n'est valable que dans une définition de méthode. Et puis, bien sûr, nous devons aussi ajouter cette méthode setter à la méthode hasName type.

En mettant tout ça ensemble, on arrive à ceci :

type hasName('a, 't) =
  {
    ..
    name: 't,
    setName: 't => 'a,
  } as 'a;

type lens('s, 'v) = {
  get: 's => 'v,
  set: ('s, 'v) => 's,
};

let nameLens: lens(hasName('a, 't), 't) = {
  get: s => s#name,
  set: (s, v) => s#setName(v),
};

let obj = {
  val name =
    "myName";

  pub name =
    name;

  pub setName = newName =>
    {<name: newName>}
};

nameLens.set(obj, "newName")
  |> nameLens.get
  |> print_endline

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X