给出以下功能
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。
答案 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 -> 'a
,get_or x:'a
get_or x:'a
应用于y
;因此'a = 'b -> 'c
x: ('b -> ' c) option
但我知道x:int
。这导致了类型检查器报告的矛盾,2
被认为是一个函数选项('a -> 'b) option
,但显然是一个int。