2

I have the following function

[@bs.obj]
external route:
  (
    ~_method: string,
    ~path: string,
    ~action: list(string) => unit,
    ~options: Js.t({..})=?,
    unit
  ) =>
  _ =
  "";

Since functions can be partially applied, I expect to be able to do this:

let get = route(~_method="GET");

but it gives me this error:

This expression's type contains type variables that can't be generalized:                                                
(~path: string, ~action: list(string) => unit, ~options: {_.. }=?,                                                       
unit) =>
{. "_method": string, "action": list(string) => unit,
  "options": Js.undefined({.. }), "path": string}

What am I doing wrong here?

glennsl
  • 28,186
  • 12
  • 57
  • 75
fhdhsni
  • 1,529
  • 1
  • 13
  • 21
  • Possible duplicate of [Why does OCaml sometimes require eta expansion?](https://stackoverflow.com/questions/25763412/why-does-ocaml-sometimes-require-eta-expansion) – glennsl Mar 22 '19 at 12:36
  • To add to the answer linked above, open object types, whether wrapped in `Js.t` or not, contain an implicit type variable, so `Js.t({..})` is in a more explicit form `Js.t({..} as 'a)`. And my guess is then that the compiler does not distinguish type variables associated with objects from any other type variable, so that from its point of view the type variable could very well be substituted with a `ref('a)`, which can potentially cause problems. – glennsl Mar 22 '19 at 12:41
  • It's not at all unlikely that someone with intimate knowledge of compiler internals will come along to correct me though, which is why I added the ocaml tag to get their attention :) – glennsl Mar 22 '19 at 12:43

1 Answers1

4

This is not actually about the optional parameters and currying, but about value restriction and non-generalized, aka weak, type variables. TL;DR; either turn get into a syntactic function by adding a parameter, e.g., let get () = route(~_method="GET") ();, or create a *.rei interface file for your module.

Longer Story

The .. row variable denotes a polymorphic type that the compiler couldn't reduce to a normal monomorphic type (since there is evidently no usage of this function) nor it can trust that the partial application route(~_method="GET") has not actually already accessed the options parameter and may be stored somewhere in it, which should define the type.

Therefore, the compiler, can't leave it as a polymorphic variable, nor can it give a concrete type, as a result, it produces a weak type variable, which could be seen as a reference cell for the future defined concrete type. Like an uninitialized type. It will be initialized later, by the code that uses the get function. If, at the end of the day, the type is never used, it may escape the scope of the module, which is forbidden by the OCaml/Reason typing rules. Therefore, you shall either give it a monotype manually (i.e., constraint it to some monomorphic type), or create an interface file where this value is hidden (i.e., not present), and therefore couldn't leak the scope of the module. Basically, just creating an empty .mli/.rei file with the same name as your .ml/.re file will resolve this issue. Another common solution is to turn get into a syntactic function, i.e., something with syntactically explicit variables, e.g.,

let get () = route(~_method="GET") ();

Further Reading

ivg
  • 34,431
  • 2
  • 35
  • 63