how to force OCaml to infer a more general type?

2019-07-22 03:41发布

问题:

I want to define a function that accepts an optional argument which is a function ('a -> 'b). The default value should be the identity, which is actually ('a -> 'a), but i see no reason why it should not be compatible with the more general ('a -> 'b). When i try:

let optional_apply ?f i =
    match f with 
    | None -> i + 4
    | Some fn -> fn (i + 4)

I always get the narrow type ?f:(int -> int) -> int -> int. But I want to keep f as int -> 'b. What can i do? Or is this just unsound, since optional_apply would not have a definite type? If so, how would I get a similar functionality?

回答1:

It is impossible, the f argument must not be optional. A simple explanation, is that optional parameters are just a syntactic sugar. So without sugar your function can be rewritten in a following form:

let optional_apply f n = match f with
  | Some f -> f (n + 4)
  | None -> (n + 4)

And here typechecker allows us only one type for f: int -> int. If it allowed us an int -> 'a type, then the None path of expression would be unsound. In other words, depending on whether f is None or Some the optional_apply will evaluate to different types. And this is considered unsound.

The best way to prove the unsoundness is to give a simple example where typechecker will allow unsound program:

let f = ref None
let g n = optional_apply ?f:!f n

with this definitions, if type checker allowed f parameter to remain polymorphic, then we can at any time "break" g function by changing f reference to anything else.



回答2:

This is not compatible with the general type ('a -> 'b). Here's why:

  • The first pattern, None -> i + 4 is of type int, thus restricting the function's return type to int.

  • The second pattern, Some fn -> fn (i + 4) must then also be of type int. Because (i + 4) is of type int, too, fn must take and return an int, thus int -> int.


The best alternative I can think of is Haskell's maybe function, of type ('a -> 'b) -> 'b -> 'a option -> 'b:

let maybe fn backup opt = match opt with
    | Some v -> fn v
    | None   -> backup
;;

... which you can probably adapt to your use case.



标签: ocaml