Error 'The type of this module contains type variables that cannot be generalized'

related to another topic (Using object in a type does not work - 'unbound value' - #9 by mudrz) (ideally I want to avoid the polymorphic 'a altogether), but why does config1 work, but config2 causes an error:

Playground Link

module MyModule = {
  type t<'a> = {value: 'a}
  let getConfig = value => {value: value}
  @send external doSomething: (int, {..}) => unit = "doSomething"
  type myFn<'a> = (int, {..} as 'a) => unit
  // works
  let config1: t<myFn<{..}>> = {value: doSomething}
  // causes an error
  let config2: t<myFn<{..}>> = getConfig(doSomething)
}

the full error is:

[ReScript] [E] The type of this module contains type variables that      
cannot be generalized:
{                                                                        
type t<'a> = {value: 'a}                                                 
let getConfig: 'a => t<'a>                                               
external doSomething: (int, {..}) => unit = "doSomething" "#rescript-
external"
type myFn<'a> = (int, 'a) => unit                                        
  constraint 'a = {..}
let config1: t<myFn<{..}>>
let config2: t<myFn<{_..}>>                                              
}                                                                        
                                                                         
This happens when the type system senses there's a mutation/side-effect, 
in combination with a polymorphic value.                                 
Using or annotating that value usually solves it.                        

notice that config2's type is let config2: t<myFn<{_..}>>, what does the _ even mean?

Hi, Rescript is not a pure language, it has some strict restrictions for value polymorphism.
(sometimes it is not needed, but compiler used to be conservative).

So for the toplevel expressions, if it is not a monomoprhic value (function is fine), the compiler will emit an error that it contains polymorphic values.

There are two ways to fix it:

  • change the value to a function to re-gain polymorphism

  • add type annotations to make it concrete type

Here, config1 works since the compiler knows doSomething is pure, and {value : } is a record so that the compiler is smart to know that {value: doSomething} is pure, so it is okay to make it polymorphic. While getConfig is a general function, the compiler won’t dig into to do the analysis, so it does not allow any polymorphic value.

You can delay it with let config2 = () => getConfig(doSomething) or make it monomorphic.

1 Like

Thanks for the clarification @Hongbo , quick question- what does it mean a “concrete type”?
Is {..} considered a polymorphic type?

From the other thread (Using object in a type does not work - 'unbound value') it does not seem to be possible to define a type that uses{..} (for example type t = {..} => unit throws an error)

Is it some magical type?

If I remember correctly, it was explained the previous thread that this is a polymorphic type. The compile error above is due to the missing type parameter. If you add the type parameter, it works: type t<'a> = {..} as 'a => unit.

Is {..} considered a polymorphic type?

Yes, there is a hidden row variable for row polymorphism.
So you can not write

type t = { ..} // compile error

you should write

type 'a t = { .. } as 'a

The confusing part may be that the row variable is implicit