Confusion regarding generic type function definition inside of another function

  let trace = (x:'a, b: string) => {
    Js.logMany([String.make(x), b])
    x
  }
let main = (): unit => {
  Console.log("Hi")

  let trace_err = trace(_, "err")
  trace("ADaS", "FD")->ignore
  trace(23, "FD")->ignore
  trace_err("DSDA")->ignore
  trace_err(314)->ignore
}

main()

So the code above works if I define trace function in global scope.
But if I put it inside the main like this, it will complains.

let main = (): unit => {
  Console.log("Hi")
let trace = (x:'a, b: string) => {
  Js.logMany([String.make(x), b])
  x
}
  trace("ADaS", "FD")->ignore
  trace(23, "FD")->ignore
}

main()

And if I remove the type notation of x here, it will also work inside of main scope

let main = (): unit => {
  Console.log("Hi")
let trace = (x, b: string) => {
  Js.logMany([String.make(x), b])
  x
}
  trace("ADaS", "FD")->ignore
  trace(23, "FD")->ignore
}

main()

I guess you have to use scoped polymorphic types for that.

Actually I can’t tell you why it differs between your examples.

2 Likes

Type variables are scoped to the top level definition, so by naming it you are forcing it to take on a single type for each invocation of the top level function.

Part of your confusion might also stem from believing that type variables enforce polymorphism. But they do not, type variables can be instantiated by any type during inference, whether general or concrete. To enforce polymorphism a type variable needs to be explicitly annotated as such, in this case with the type annotation 'a. ('a, string) => 'a.

4 Likes

I must say I don’t understand what you mean

Type variables are scoped to the top level definition so by naming it you are forcing it to take on a single type for each invocation of the top level function.

the top level function in question is main(), so with each main() invocation, I only get one type of 'a ? then why does the trace function defined in global scope work? wouldn’t it just be 1 type 'a for the main invocation as well?

Also, why the trace function when defined inside main scope without 'a type for x work ?

let main = ()=>{
  let trace = (x, b: string) => {
    Js.logMany([String.make(x), b])
    x
  }
  //....
}

I read the Doc and it says

Scoped Polymorphic Types in ReScript are functions with the capability to handle arguments of any type within a specific scope

Does this imply that global scope is not a “specific scope”? (Because you didn’t need to use the Scoped Polymorphic type syntax for functions that are defined in global scope, for it to be polymorphic)
I don’t understand anything :frowning:

It means that the type is inferred when the top level function, main is invoked. Not when trace is invoked (when defined inside main). You can think of it as main having a hidden type parameter:

let main = (_: 'a) => {
  let trace = (x: 'a, b: string) => ...
  ...
}

The 'as here refer to the same type variable, and its concrete type will therefore need to be determined when main is invoked.

Because the type variable hasn’t been named. I can only speculate on why it works like that, but my guess would be that without a name it can’t be referred to in main and its type can therefore be inferred independently.

I don’t know what “specific scope” means. The real source of truth for language semantics is usually the OCaml manual, since ReScript is based on OCaml:

In general the scope of a named type variable is the whole top-level phrase where it appears, and it can only be generalized when leaving this scope. Anonymous variables have no such restriction. In the following cases, the scope of named type variables is restricted to the type expression where they appear: 1) for universal (explicitly polymorphic) type variables; 2) for type variables that only appear in public method specifications (as those variables will be made universal, as described in section ‍11.9.1); 3) for variables used as aliases, when the type they are aliased to would be invalid in the scope of the enclosing definition (i.e. when it contains free universal type variables, or locally defined types.)

https://ocaml.org/manual/5.2/types.html#sss:typexpr-variables

1 Like

I see. While I can accept it for what it is, it still feels really inconsistent. I would expect the global scope function with 'a to not having polymorphism unless explicitly stated as well.

I don’t see how it’s inconsistent. Types are inferred to be as general as possible given the constraints. At the top-level, there are no constraints and the most general type is polymorphic. As an inner function where you explicitly constrain it by assigning a type variable that is scoped to the outer function, it can only be generalized outside that scope. On the inside, the type variable needs to refer to a single concrete type across the entire scope. Otherwise it wouldn’t be sound.

You can of course disagree with the scoping rules. It would certainly be feasible to have type variables scoped to where it first appears. for example. But that would mean that type variables could change their scope, and therefore their meaning, if the same name is used in an outer scope. Personally, I don’t see that being a better solution.

1 Like

wait I think I got what you mean.
So every invocation is an expression

let a = 4 // Top level Expression 1
Console.log(55) // Top level Expression 2
Console.log("whatever") // Top level Expression 3

switch a {
| 4 => "four"
| _ => "not four"
}->Console.log // Top level Expression 4

let main = { // Like you said, think of it like let main = (_:'a){
  let returnSelf = (x: 'a) => x
  let print = (v:'a) => Console.log(v)
  print(33) //here 'a is type int when it's called by Expression 6
  returnSelf("ad")->Console.log //ERR, because 'a is int now in  Expression 6 main()invocation
} // Top level Expression 5

main() // Top level Expression 6

So at each of those top level expression, they get 1 type for the type variable 'a.
Top Level Expression 6 is main() invocation, it will get one type 'a that will be inferred as int due to its first usage.

THANK YOU!! I think it finally makes sense.

3 Likes