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

时间:2019-02-05 17:02:48

标签: types polymorphism ocaml reason

我注意到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 :(得分:5)

根本问题是您的函数runProgram是第二级多态的,换句话说,使用多态函数作为参数会有点复杂。

更严重的是,在幻想语法中,runProgram的类型为('a. 'a => 'a)=> unit,其中'a. 'a => 'a表示对任何'a都起作用的函数。与此类似的功能

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

,其中类型变量'a首先被引入(在prenex位置),然后要求function参数仅对该特定类型'a起作用。例如

 let two = apply( (x)=> 1 + x, 1)
即使(x)=> 1 + x仅适用于整数,

也有效。而

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

失败,因为(x) => 1 + x无法使用字符串。

回到类型推断问题,类型检查器未能推断出您所记住的类型的原因是类型推断和更高级别的多态性不能很好地融合(更确切地说,在存在更高级别的情况下,类型推断是无法确定的) -rank多态性)。要了解原因,请考虑使用此简单功能

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手册。

答案 1 :(得分:1)

我不是类型推断算法的专家,但是对我来说,第一种情况下它可以工作是很奇怪的,因为类型变量是在记录中定义的,而不仅仅是为函数定义的。考虑一下如果您向idRecord类型的'a添加另一个字段会发生什么情况:

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

我怀疑它的工作是由于放宽了类型推断规则,该规则仅在某些非常有限的条件下有效,其中不包括将记录作为函数参数。

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

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

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