Search code examples
ocamlreasonpartial-applicationvalue-restriction

reasonml type higher order function


given the following module the compiler raises an error

  41 │ };
  42 │ 
  43 │ module TestB = {
  44 │   let minFn = (a, b) => a < b ? a : b;
   . │ ...
  54 │   let max = reduceList(maxFn);
  55 │ };
  56 │ 
  57 │ // module Number = {

  The type of this module contains type variables that cannot be generalized:
  {
    let minFn: ('a, 'a) => 'a;
    let maxFn: ('a, 'a) => 'a;
    let reduceList: ('a, list('b)) => option('b);
    let min: list('_a) => option('_a);
    let max: list('_a) => option('_a);
  }

This seems to happen because I'm only partially applying the arguments to reduceList. Now, I've been provided some information about value restriction and it seems to me that this is what's happening here.

I've already tried explicitly typing the functions min and max where they are defined and to explicitly type the module as a whole because I thought that's how you're supposed to get around this according to this section about value restriction. However, this seems to make no difference.

module TestB = {
  let minFn = (a, b) => a < b ? a : b;
  let maxFn = (a, b) => a > b ? a : b;
  let reduceList = (comp, numbers) =>
    switch (numbers) {
    | [] => None
    | [head] => Some(head)
    | [head, ...tail] => Some(List.fold_left(minFn, head, tail))
    };

  let min = reduceList(minFn);
  let max = reduceList(maxFn);
};

On another note... does the leading _ for types mean anything special here?


Solution

  • This is indeed because of the value restriction. I can't see that the section of documentation you refer to says anything about being able to use a type annotation to avoid it however, although it does seem to me like it should as well. Hopefully some experienced OCamler here can explain why it doesn't.

    As far as I know, unless the type annotation does not contain any type variables, thereby removing the polymorphism, it won't, and I don't think that's what you want. The easiest way to fix this is to use eta expansion, that is to make the argument explicit instead of using partial application. This demonstrates both:

    module TestB = {
      let minFn = (a, b) => a < b ? a : b;
      let maxFn = (a, b) => a > b ? a : b;
      let reduceList = (comp, numbers) =>
        switch (numbers) {
        | [] => None
        | [head] => Some(head)
        | [head, ...tail] => Some(List.fold_left(minFn, head, tail))
        };
    
      let min = x => reduceList(minFn, x);
      let max : list(int) => option(int) = reduceList(maxFn);
    };
    

    The underscore, _ in '_a just means it's a weak type variable, as explained in the documentation you refer to:

    Type variables whose name starts with a _weak prefix like '_weak1 are weakly polymorphic type variables, sometimes shortened as weak type variables. A weak type variable is a placeholder for a single type that is currently unknown. Once the specific type t behind the placeholder type '_weak1 is known, all occurrences of '_weak1 will be replaced by t.