Как мне указать на поле записи в случае расширяемых типов объектов? - PullRequest
3 голосов
/ 17 мая 2019

Думая о способе определения простой линзы на объекте в Reason.

Я пытаюсь использовать расширяемые объекты (с добавлением .. к списку полей) со следующим кодом:

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},
}

Я получаю «Имя поля записи не может быть найдено». ошибка, хотя тип hasName обязательно должен иметь один ... Что я здесь не так делаю?

Отказ от ответственности: я новичок в Reason / OCaml, поэтому я могу упустить некоторые очевидные вещи.

1 Ответ

5 голосов
/ 17 мая 2019

Сообщение об ошибке не велико (мягко говоря), но если вы внимательно прочитаете, это действительно указывает на проблему.Он говорит: « поле записи имя не может быть найдено», что не так уж странно, учитывая, что у вас есть объект, а не запись.Это ошибка, потому что доступ к членам объекта осуществляется с помощью #, а не . (см. Документация Reason по объектам ).

Как я понимаю, причина этого заключается в том, чтоOCaml не поддерживает специальный полиморфизм (т. Е. Перегрузку функций или операторов), поэтому для правильной работы вывода типов необходимо синтаксически различать операции над различными типами.Я также подозреваю, что причина, по которой он не просто говорит «чувак, это объект, а не запись», заключается в том, что система типов не имеет понятия any-record-type, но требует определенного типа записи.Таким образом, он пытается сначала найти поле, чтобы затем определить конкретный тип записи, связанный с ним.Если он не может найти поле, он не считается ошибкой типа, потому что нет подходящего типа для конфликта с ним.

В любом случае, метод получения решается достаточно легко, используя # вместо ..Однако сеттер более проблематичен.Здесь вы также используете синтаксис обновления записей и получите похожую ошибку, но, к сожалению, прямого эквивалента для объектов нет.

Одна часть того, что делает объекты объектно-ориентированными, заключается в том, что их реализация скрыта.Вы не можете скопировать объект извне, если не знаете, что внутри.И хотя вы можете создать другой объект, соответствующий тому же типу объекта, вы можете сделать это, только если знаете конкретный тип.Что в данном случае вам не нужно.

Таким образом, способ неизменного обновления объекта состоит в том, чтобы сделать это изнутри объекта в методе установки, где вы знаете все, что вам нужно знать.Синтаксис для неизменного обновления текущего объекта: {<name: newName>}, который действителен только в определении метода.И затем, конечно, нам также нужно добавить этот метод установки к типу hasName.

Собрав все это вместе, мы получим следующее:

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
...