Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New internal representation for uncurried types. #5870

Merged
merged 20 commits into from
Dec 8, 2022

Conversation

cristianoc
Copy link
Collaborator

@cristianoc cristianoc commented Dec 2, 2022

The representation is making use of a built-in type that corresponds to the following declaration:

@unboxed
type function$<'fn, 'arity> = Function$('fn)

A function definition (. x, y) => x+y is encoded as:

@res.arity(2) Function$((x,y) => x+y)

whose type becomes:

function$<(int, int) => int, [#Has_arity2]>

@bobzhang
Copy link
Member

bobzhang commented Dec 4, 2022

We can create a builtin type, called function or fun since this is not limited to Js
To encode the arity, we can use the structural types, the tuple works but it is a bit scary for large arities.
An example would be

('a , `Has_arity5 )function

Using a keyword would help to avoid aliases, which make our ad-hoc error message improvement more robust

@cristianoc
Copy link
Collaborator Author

cristianoc commented Dec 5, 2022

There's an issue with using a constraint to give the arity to function definitions. E.g. (. x, y) => x+y is represented asJs.Uncurried((x,y) => x+y) : Js.uncurried<_ #2>.
The issue is that when this is used e.g. in an applied context foo( (. x, y) => x+y ) the type of the argument of foo is not propagated to the body of the function while it is typed, because the constraint stops the propagation.

Now considering to instead pass the arity via an annotation, which is then picked up by the type checker.

@cristianoc
Copy link
Collaborator Author

The current representation is making use of this type:

(* In js.ml *)
type ('fn, 'arity) uncurried = Uncurried of 'fn [@unboxed]

A function definition (. x, y) => x+y is encoded as:

@res.arity(2) Js.Uncurried((x,y) => x+y)

whose type becomes:

Js.uncurried<(int, int) => int, [#2]>

This seems to work well on all the examples now.
There are questions about naming and using a built-in type. The encoding relies on having a unary variant (with special treatment in the type checker for the arity attribute).

@cristianoc
Copy link
Collaborator Author

Now using built-in type uncurried$ with one variant case called Uncurried$ (neither function not fun are keywords).

@cristianoc cristianoc force-pushed the different_uncurried_representation branch from de65464 to 9e2b411 Compare December 6, 2022 10:16
@bobzhang
Copy link
Member

bobzhang commented Dec 7, 2022

Some comments:

  1. I would like to have #Has_arity5 instead of #5, the further is more meaningful when our ad-hoc error message handling is not robust
  2. Uncurried$ is academic, we can call it function or function$
  3. We should make an abstract type without using a variant, type ('fn, 'arity function ) without exposing it as a concrete type

@bobzhang
Copy link
Member

bobzhang commented Dec 7, 2022

The issue is that when this is used e.g. in an applied context foo( (. x, y) => x+y ) the type of the argument of foo is not propagated to the body of the function while it is typed, because the constraint stops the propagation.

I suggest we adapt the type checker to do the propagation.

The current encoding is unsafe, since user can also create uncurried type by hand. Another caveat is when do such unsafe encodings, make sure the internal optimization will not change the arity, so you do need some opaque operations to prevent such optimizations

@cristianoc
Copy link
Collaborator Author

  1. We should make an abstract type without using a variant, type ('fn, 'arity function ) without exposing it as a concrete type

The variant is part of the encoding. Used for function definitions (and a few places in the compiler pipeline).

@cristianoc
Copy link
Collaborator Author

cristianoc commented Dec 7, 2022

The issue is that when this is used e.g. in an applied context foo( (. x, y) => x+y ) the type of the argument of foo is not propagated to the body of the function while it is typed, because the constraint stops the propagation.

I suggest we adapt the type checker to do the propagation.

The current encoding is unsafe, since user can also create uncurried type by hand. Another caveat is when do such unsafe encodings, make sure the internal optimization will not change the arity, so you do need some opaque operations to prevent such optimizations

The arity is indeed set by the type checker at the moment, by taking @res.arity2 Uncurried$(...) and setting the arity to [#2]so effectively the type constraint is in place, but does not prevent type propagation to the function type (which an explicit type constraint would).
I have not shown the encoding of application, which has not changed here, and still uses opaqueFullApply and opaque.

@cristianoc
Copy link
Collaborator Author

cristianoc commented Dec 7, 2022

Not sure I understand the comment about creating a type by hand. This is what user could do by hand before:

type fun2 = Js.Fn.arity2<(int, int) => int>;

Unclear what the new problem is.

@bobzhang
Copy link
Member

bobzhang commented Dec 7, 2022

@cristianoc I reread the encoding, the encoding looks good to me modulo some comments about the naming, it is a nice trick to generate the phantom variant types.

What is the implication for the vanilla ocaml syntax?

@cristianoc
Copy link
Collaborator Author

cristianoc commented Dec 7, 2022

What is the implication for the vanilla ocaml syntax?

The compiler code is refactored so the runtime representation of uncurried function definitions and types live in a single file. In the case of ocaml functions, the compiler's ppx calls into that file, which generates the same definitions as with the ReScript syntax. So the vanilla ocaml syntax, e.g. in some compiler libs, stays the same, and produces the same code as if it were written in ReScript.

@cristianoc cristianoc force-pushed the different_uncurried_representation branch from faa7363 to c513303 Compare December 7, 2022 18:55
@bobzhang bobzhang self-assigned this Dec 8, 2022
@bobzhang bobzhang removed the APPROVED label Dec 8, 2022
@cristianoc cristianoc force-pushed the different_uncurried_representation branch from c513303 to 2aa76c5 Compare December 8, 2022 08:29
@cristianoc cristianoc changed the title Rough test of different runtime representation for uncurried types. New internal representation for uncurried types. Dec 8, 2022
Only for arity 5.
The representation of `(. int, int) => string` would be `Js.uncurried( ((int,int)=>int,  [unit, unit])` where the second type parameter encodes the arity.
Does not require to pre-declare all the arities.
Using a constraint to determine the arity in a function definition is problematic as the constraint limits type propagation during inference.
Instead, pass the arity via an attribute @res.arity on the Js.Uncurried variant.
And specialize the type checker to handle that arity and use type unification to propagate the arity.
@cristianoc cristianoc force-pushed the different_uncurried_representation branch from 3e9cbc4 to 6721cd1 Compare December 8, 2022 10:35
@cristianoc cristianoc merged commit eb07cb5 into master Dec 8, 2022
@cristianoc cristianoc deleted the different_uncurried_representation branch December 8, 2022 11:11
@cristianoc cristianoc mentioned this pull request Dec 8, 2022
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants