Как написать функцию в ReasonML с переменной типа для приема любого типа параметра?

Я заметил очень странное поведение механизма вывода типов в ReasonML. У меня есть запись, содержащая функцию идентификации. Когда я использую экземпляр записи напрямую, компилятор не жалуется. Но когда я передаю запись другой функции и пытаюсь вызвать функцию идентификации, вывод типа жалуется:

type idRecord('a) = {
  // idFn can take any type.
  idFn: 'a => 'a
};

let myRecord: idRecord('a) = {
  idFn: anyVal => anyVal
};

// WORKS ABSOLUTELY FINE
let x1 = myRecord.idFn(10);
let x2 = myRecord.idFn("Something");

let runProgram = (program: idRecord('a)) => {

  let _y1 = program.idFn(10);

  // BOOM: ERROR
  // This expression has type string but an expression was expected of type int
  let _y2 = program.idFn("Something");
}

runProgram(myRecord);

Ошибка:

Это выражение имеет тип string, но ожидалось выражение типа int

Что мне нужно, чтобы вывод типов мог принимать аргументы любого типа?


person Harshal Patil    schedule 05.02.2019    source источник


Ответы (2)


Я не специалист по алгоритму вывода типов, но мне кажется странным, что он работает в первом случае, поскольку переменная типа определена в записи, а не только для функции. Подумайте, что произойдет, если вы добавите еще одно поле в idRecord типа 'a:

type idRecord('a) = {
  idFn: 'a => 'a,
  value: 'a
};

Я подозреваю, что это работает из-за ослабления правил вывода типов, которые работают только в некоторых очень ограниченных условиях, которые не включают запись, являющуюся аргументом функции.

В любом случае решение простое: удалите переменную типа из записи и универсально определите 'a в сигнатуре типа функции:

type idRecord = {
  idFn: 'a. 'a => 'a
};

'a., который следует читать "для всех 'a", гарантирует, что 'a полностью полиморфен и будет принимать любой тип.

person glennsl    schedule 05.02.2019

Основная проблема заключается в том, что ваша функция runProgram является полиморфной второго ранга, или, другими словами, использование полиморфной функции в качестве аргумента немного сложно.

Более серьезно, в синтаксисе фэнтези тип runProgram будет ('a. 'a => 'a)=> unit, где 'a. 'a => 'a обозначает функцию, которая требуется для работы для любого 'a. Это контрастирует с такой функцией, как

let apply: 'a. ('a -> 'a) -> 'a -> 'a = (f, x) => f(x)

где переменная типа 'a вводится первой (в предвешенной позиции), а затем требуется, чтобы аргумент функции работал только с этим конкретным типом 'a. Например

 let two = apply( (x)=> 1 + x, 1)

действительно, даже если (x)=> 1 + x работает только для целых чисел. В то время как

 let fail = runProgram((x) => 1 + x)

не работает, потому что (x) => 1 + x не может работать со строками.

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

let ambiguous(f,x) = f(1)+f(x)

Тип, выведенный средством проверки типов для ambiguous, - (int=>int)=>int=>int. Однако, если я заменю f записью с полиморфным полем (что является одним из двух способов написать полиморфную функцию высшего порядка в OCaml)

type const = {f:'a. 'a => int}
let ambiguous({f},x) = f(1)+f(x)

тип ambiguous (в синтаксисе фэнтези) становится ('a.'a=>int)=>'a=>int. Другими словами, если бы вывод типа мог вывести полиморфизм более высокого ранга, ему пришлось бы выбирать между ('a.'a=>int)=>'a=>int и (int=>int)=>int=>int. И нет явного победителя между двумя типами: первый тип имеет сильные ограничения на свой первый аргумент и слабее на второй аргумент, а второй тип - с точностью до наоборот. Это общая проблема с полиморфизмом более высокого ранга: существует множество возможных вариантов без очевидного лучшего выбора.

Вот почему проверка типов требует, чтобы при написании полиморфной функции более высокого ранга использовался явный указатель:

type program = { program: 'a. 'a => 'a }
let runProgram = ({program}) => {
  let _y1 = program(10);
  let _y2 = program("Something");
}

См. Также руководство по OCaml по адресу http://caml.inria.fr/pub/docs/manual-ocaml/polymorphism.html#sec61.

person octachron    schedule 06.02.2019