Как написать функцию в ReasonML с переменной типа, чтобы принять любой тип параметра? - PullRequest
0 голосов
/ 05 февраля 2019

Я замечаю очень странное поведение с механизмом вывода типов в 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);

Ошибка:

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

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

Ответы [ 2 ]

0 голосов
/ 06 февраля 2019

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

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

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

, где сначала вводится переменная типа 'a (в позиции prenex), а затем аргумент функции требуется для работы только с этим конкретным типом '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.

0 голосов
/ 06 февраля 2019

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

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

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

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

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

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

...