在函数调用中省略参数标签时类型推断失败可怕

时间:2017-09-24 20:23:06

标签: label parameter-passing ocaml type-inference

给出以下功能

let get_or ~default =
  function | Some a -> a
           | None -> default

如果使用标记的参数调用此函数,它将按预期工作:

let works = get_or ~default:4 (Some 2)

但如果省略标签,它就会失败:

let fails = get_or 4 (Some 2)

然而,更奇怪的是,编译器在此处给出的错误消息是:

  

此表达式的类型为int,但表达式需要类型(' a - >' b)选项

编译器不仅错误地推断它是一个选项,而且由于某种原因,它还从众所周知的魔术师的帽子中提取了一个函数类型!所以我很自然地想知道:它究竟来自哪里?对我的好奇心不那么重要,为什么在这个具体案例中没有省略标签的工作呢?

有关交互式示例,请参阅this reason playground

这个谜题归功于Reason Discord上的@nachotoday。

1 个答案:

答案 0 :(得分:7)

这是标记参数,currying和first class函数之间的破坏性干扰的情况。函数get_or具有类型

 val get_or: default:'a -> 'a option -> 'a 

带有标签参数的规则是当应用程序为total时可以省略标签。乍一看,这意味着如果将get_or应用于两个参数,那么它就是一个完整的应用程序。但是get_or的返回类型是多态的(aka 'a)这一事实会带来麻烦。考虑一下:

let id x = x
let x : _ -> _ = get_or (Some id) id ~default:id

这是有效的代码,其中get_or应用于三个参数,并且默认参数在第三个位置提供!

更进一步,令人惊讶的是,这仍然有效:

let y : default:_ -> _ -> - = get_or (Some id) id id

它为y产生了一个相当复杂的类型。

这里的通用规则是,如果函数的返回类型是多态的,那么类型检查器永远不会知道函数应用程序是否为total;因此标签永远不会被忽略。

回到你的例子,这意味着类型检查器读取

 get_or (4) (Some 2)

作为

  • 首先,get_or适用于default:'a -> 'a option -> 'a类型。 尚未提供默认标签, 因此结果将包含类型default:'r -> 'r
  • 查看r,在get_or 2 (Some 4)中,get_or包含类型 'a option -> 'aget_or x:'a
  • 然后get_or x:'a应用于y;因此'a = 'b -> 'c
  • 换句话说,我应该x: ('b -> ' c) option 但我知道x:int

这导致了类型检查器报告的矛盾,2被认为是一个函数选项('a -> 'b) option,但显然是一个int。