Types 如何使用类型变量在ReasonML中编写函数以接受任何类型的参数?

Types 如何使用类型变量在ReasonML中编写函数以接受任何类型的参数?,types,polymorphism,ocaml,reason,Types,Polymorphism,Ocaml,Reason,我注意到ReasonML中的类型推断机制有一个非常奇怪的行为。我有一个包含标识功能的记录。当我直接使用记录实例时,编译器不会抱怨。但当我将记录传递给另一个函数并尝试调用identity函数时,类型推断会抱怨: type idRecord('a) = { // idFn can take any type. idFn: 'a => 'a }; let myRecord: idRecord('a) = { idFn: anyVal => anyVal }; // WORK

我注意到ReasonML中的类型推断机制有一个非常奇怪的行为。我有一个包含标识功能的记录。当我直接使用记录实例时,编译器不会抱怨。但当我将记录传递给另一个函数并尝试调用identity函数时,类型推断会抱怨:

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


我需要什么才能使类型推断愉快地接受任何类型的参数?

我对类型推断算法不是专家,但对我来说,它在第一种情况下工作似乎很奇怪,因为类型变量是在记录上定义的,而不仅仅是函数。考虑如果您添加另一个字段到<代码> IDCords>代码> > < < /代码>:

type idRecord('a) = {
  idFn: 'a => 'a,
  value: 'a
};
我怀疑它之所以有效,是因为类型推断规则的放宽,这种规则只适用于一些非常有限的条件,其中不包括作为函数参数的记录

在任何情况下,解决方案都很简单:从记录中删除类型变量,并在函数类型签名中普遍量化
'a

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

'a.
,应该读为“for all
'a
”,确保
'a
是完全多态的,可以接受任何类型。

我对类型推断算法不是很精通,但对我来说,它在第一种情况下工作似乎很奇怪,因为类型变量是在记录上定义的,而不仅仅是函数。考虑如果您添加另一个字段到<代码> IDCords>代码> > < < /代码>:

type idRecord('a) = {
  idFn: 'a => 'a,
  value: 'a
};
我怀疑它之所以有效,是因为类型推断规则的放宽,这种规则只适用于一些非常有限的条件,其中不包括作为函数参数的记录

在任何情况下,解决方案都很简单:从记录中删除类型变量,并在函数类型签名中普遍量化
'a

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

'a.
,应该读为“对于所有
'a
s”,确保
'a
是完全多态的,并且可以接受任何类型。

根本问题是,您的函数
运行程序
是二级多态的,或者换句话说,使用多态函数作为参数有点复杂

更严重的是,在幻想语法中,
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
无法处理字符串

回到您的类型推断问题,typechecker未能推断出您心目中的类型的原因是,类型推断和较高秩多态性没有很好地匹配(更准确地说,在存在较高秩多态性的情况下,类型推断是不可判定的)。为了理解为什么,考虑这个简单函数

let ambiguous(f,x) = f(1)+f(x)
类型检查器为
不明确
推断的类型是
(int=>int)=>int=>int
。 但是,如果我用带有多态字段的记录替换
f
(这是在OCaml中编写高阶多态函数的两种方法之一)

不明确的类型(在幻想语法中)变成
('a.'a=>int)=>'a=>int
。换句话说,如果类型推断能够推断出更高级别的多态性,那么它必须在
('a.'a=>int)=>'a=>int
(int=>int)=>int=>int
之间做出决定。这两种类型之间没有明显的赢家:第一种类型对其第一个参数有很强的约束,而对其第二个参数则较为宽松,而第二种类型正好相反。这是一个具有较高等级多态性的一般性问题:有很多潜在的选择,但没有明显的最佳选择

这就是为什么typecheker在编写高阶多态函数时需要非常明确:

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

另请参见位于的OCaml手册。

根本问题是,您的函数
运行程序
是二阶多态的,或者换句话说,使用多态函数作为参数有点复杂

更严重的是,在幻想语法中,
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
无法处理字符串

回到您的类型推断问题,typechecker未能推断出您心目中的类型的原因是,类型推断和较高秩多态性没有很好地匹配(更准确地说,在存在较高秩多态性的情况下,类型推断是不可判定的)。为了理解为什么,考虑这个简单函数

let ambiguous(f,x) = f(1)+f(x)
类型检查器为
不明确
推断的类型是
(int=>int)=>int=>int
。 但是,如果我用带有多态字段的记录替换
f
(这是在OCaml中编写高阶多态函数的两种方法之一)

不明确的类型(在幻想语法中)变成
('a.'a=>int)=>'a=>int
。换句话说,如果类型推断能够推断出更高级别的多态性,那么它必须在
('a.'a=>int)=>'a=>int
(int=>int)=>int=>int
之间做出决定。没有c