Search code examples
polymorphismocaml

Ocaml polymorphic records type is less general


Given the following type:

 type ('props,'state) reactInstance =
  {
  props: 'props;
  state: 'state;
  updater:
    'event .
      (('props,'state) reactInstance -> 'event -> 'state) ->
        ('props,'state) reactInstance -> 'event -> unit;}

I'm trying to achieve:

let rec updater f instance event =
  let nextState = f instance event in
  let newInstance =
    { props; state = nextState; updater } in
  ()

let newInstance =
  { props; state = (reactClass.getInitialState ()); updater }

I gave the updater a forall-like type definition. My main motivation is because the updater will get invoked with an event. No idea what that event will be beforehand. It could be a click on the user interface or key press etc.

The problem ocurring inside the updater definition on { props; state = nextState; **updater** } :

Error: This field value has type
         (React.reactInstance props#1618 state#1200 => 'a => state#1200) =>
React.reactInstance props#1618 state#1200 => 'a => unit
       which is less general than
         'event.
  (React.reactInstance 'props 'state => 'event => 'state) =>
  React.reactInstance 'props 'state => 'event => unit

Why does this occur inside the let rec updater... on updater and not when defining the record with updater in let newInstance? How do I get around this?


Solution

  • You are doing what is called "polymorphic recursion". This is a recursive function that could be invoked on different type at each recursion loop. In your case, it's not much different type, but putting the function into a container with a forall.

    Polymorphic recursion is known to be undecidable to infer, so you need to help the typechecker a bit by using polymorphic annotation. In this case, you also need to eta expand the instance function (see ivg's other answer). Here is the final result. Note that your function was missing a parameter.

    type ('props,'state) reactInstance = {
      props: 'props;
      state: 'state;
      updater:
        'event .
          (('props,'state) reactInstance -> 'event -> 'state) ->
        ('props,'state) reactInstance -> 'event -> unit;}
    
    let rec updater
      : 'event .
        'props ->
        (('props,'state) reactInstance -> 'event -> 'state) ->
        ('props,'state) reactInstance -> 'event -> unit
      = fun props f instance event ->
        let nextUpdater f i e = updater props f i e in
        let nextState = f instance event in
        let newInstance =
          { props; state = nextState; updater = nextUpdater } in
        ()