Search code examples
ocamltype-inferencevalue-restriction

Why type inference algorithm confuses because of 'Fun.flip Option.bind'?


The common signature of functions declarations in a module is when the last parameter has a type of the main state (Module.t). Like it is in the 'List' module. This form opens the ability to use '|>' operator like:

[1;2;3] |> List.filter ((>)2)
        |> List.map ((-)1)
        |> List.fold_left 0 (+)

But function 'bind' in 'Option' module doesn't follow this form. It has 'Option.t' parameter as the first

val bind : 'a option -> ('a -> 'b option) -> 'b option

But ok, I can change it. I declared function 'opt_bind' with the reverse order of parameters.

let opt_bind = Fun.flip Option.bind

But this one doesn't work. And the following code was compiled with the following error

type a = A of int
type b = B of int 

let f x = Some (A x)
let g (A x) = Some (B x)  
let opt_bind = Fun.flip Option.bind 

let result = 
  (Some 42) |> opt_bind f
            |> opt_bind g
         |> opt_bind g
                     ^                     

Error: This expression has type a -> b option but an expression was expected of > type int -> a option. Type a is not compatible with type int

The same situation with

let result = 
  let x = opt_bind f (Some 42) in
  let x = opt_bind g x in
  x 

Even after I had noticed all types I still have the same problem.

let f : int -> a option = fun x -> Some (A x)
let g : a -> b option = fun (A x) -> Some (B x)  
let opt_bind : ('a -> 'b option) -> 'a option -> 'b option = 
  Fun.flip Option.bind 

let result : b option = 
  let x : a option = opt_bind f (Some 42) in
  let x : b option = opt_bind g x in
  x ;;

But

let result = 
  let x = Option.bind (Some 42) f in
  let x = Option.bind x g in
  x 

works fine.

Why does 'opt_bind' have the wrong type expectation for 'g' as if 'opt_bind' isn't generic?
How to use 'bind' with '|>' notation?


Solution

  • Your problem is that your defintion of opt_bind isn't polymorphic enough. Because you define it as an application (of Fun.flip to Option.bind), it can't be made polymorphic due to the value restriction.

    If you define it like this:

    let opt_bind a b = Fun.flip Option.bind a b
    

    or, equivalently, like this:

    let opt_bind a b = Option.bind b a
    

    then things will work.

    If you ask for the type of your opt_bind definition you'll see the problem:

    # let opt_bind = Fun.flip Option.bind;;
    val opt_bind :
      ('_weak3 -> '_weak4 option) -> '_weak3 option ->
      '_weak4 option = <fun>
    

    The "weak" type variables are telling you that the resulting function is not polymorphic.

    The essential difference is that Fun.flip Option.bind is syntactically an application (a function call). Such an expression can't be made polymorphic. The two alternate forms define bind_opt as a lambda (a function value), which is a syntactic "value" in the terminology of the value restriction.

    The value restriction is required to make sure that polymorphic functions are sound (i.e., that they don't allow inappropriate operations on values).

    My reference of choice for the value restriction (especially as implemented in OCaml) is this paper: Relaxing the Value Restriction, Jacques Garrigue