如何使用类型变量在 ReasonML 中编写函数以接受任何类型的参数?
How to write a function in ReasonML with a type variable to accept any type of parameter?
我注意到 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);
错误是:
This expression has type string but an expression was expected of type int
我需要什么才能使类型推断乐于接受任何类型的参数?
我不是类型推断算法方面的专家,但对我来说,它在第一种情况下起作用似乎很奇怪,因为类型变量是在记录中定义的,而不仅仅是为函数定义的。考虑一下如果您将另一个字段添加到类型 'a
:
的 idRecord
会发生什么
type idRecord('a) = {
idFn: 'a => 'a,
value: 'a
};
我怀疑它之所以起作用,是因为放宽了类型推断规则,该规则仅在某些非常有限的条件下起作用,其中不包括作为函数参数的记录。
无论如何,解决方法很简单:从记录中删除类型变量并在函数类型签名中通用量化'a
:
type idRecord = {
idFn: 'a. 'a => 'a
};
'a.
,应该读作 "for all 'a
s",确保 'a
是完全多态的并且将接受任何类型。
根本问题是你的函数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");
}
另请参阅 http://caml.inria.fr/pub/docs/manual-ocaml/polymorphism.html#sec61 处的 OCaml 手册。
我注意到 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);
错误是:
This expression has type string but an expression was expected of type int
我需要什么才能使类型推断乐于接受任何类型的参数?
我不是类型推断算法方面的专家,但对我来说,它在第一种情况下起作用似乎很奇怪,因为类型变量是在记录中定义的,而不仅仅是为函数定义的。考虑一下如果您将另一个字段添加到类型 'a
:
idRecord
会发生什么
type idRecord('a) = {
idFn: 'a => 'a,
value: 'a
};
我怀疑它之所以起作用,是因为放宽了类型推断规则,该规则仅在某些非常有限的条件下起作用,其中不包括作为函数参数的记录。
无论如何,解决方法很简单:从记录中删除类型变量并在函数类型签名中通用量化'a
:
type idRecord = {
idFn: 'a. 'a => 'a
};
'a.
,应该读作 "for all 'a
s",确保 'a
是完全多态的并且将接受任何类型。
根本问题是你的函数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");
}
另请参阅 http://caml.inria.fr/pub/docs/manual-ocaml/polymorphism.html#sec61 处的 OCaml 手册。