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

Uncurried always #5968

Merged
merged 10 commits into from
Mar 16, 2023
Merged

Uncurried always #5968

merged 10 commits into from
Mar 16, 2023

Conversation

cristianoc
Copy link
Collaborator

@cristianoc cristianoc commented Feb 1, 2023

Add support for uncurried-always: a special mode where all functions definitions and applications are considered uncurried, ignoring whether . is used.

  • Added a project config {"uncurried": ...}, which propagates to dependencies, and takes the values: "legacy" which changes nothing, or "default" for uncurried by default, or "always" for uncurried-always mode.

  • Introduce automatic curried application in uncurried-always mode: when a function with curried type is used in an application, the application is treated as curried application. E.g. Belt.List.map([]) works even though technically the application is uncurried.

  • Introduce a form of subtyping in uncurried-always mode: uncurried functions are cast to curried ones during unification.

  • Introduce a fallback mechanism @res.partial to force partial application: @res.partial foo(x, y) is treated as partial application. This is the only way to express partial application of uncurried functions in uncurried-always mode.

For example, existing code could use Js.Array2.map([1], x => x+1). In the new mode, this is an uncurried application, and the callback is a curried function. Both mechanisms are uses, so the application is treated as curried application, and the callback function is cast to an uncurried one.

@cristianoc cristianoc changed the base branch from master to makefile February 1, 2023 10:11
Base automatically changed from makefile to master February 1, 2023 10:12
@cristianoc cristianoc requested a review from zth February 1, 2023 10:15
@cristianoc cristianoc force-pushed the uncurried_always branch 4 times, most recently from 5c1f006 to 6dbc4d1 Compare February 4, 2023 10:19
@cristianoc
Copy link
Collaborator Author

@mununki are the changes to support uncurried functions in JSX V4 also possible for V3, or is the new data representation that makes this possible?
Just curious about the answer for now, unclear whether this is something one would want for real legacy code.

@mununki
Copy link
Member

mununki commented Feb 4, 2023

@mununki are the changes to support uncurried functions in JSX V4 also possible for V3, or is the new data representation that makes this possible? Just curious about the answer for now, unclear whether this is something one would want for real legacy code.

Afraid to say so. I don't think V3 would work with the new data representation introduced by uncurried.
I remember making V4 compatible with the new data representation was kinda simple. I can put the same work on V3 if you think it is needed. Why not make V3 support the uncurried mode?

@cknitt
Copy link
Member

cknitt commented Feb 5, 2023

Just got curious and tried the following:

  • download the last PR build
  • install it into one of our projects
  • build in legacy mode => works fine
  • set "uncurried": "always" in bsconfig.json and rebuild

This gave me an error here:

type t
let onEvent: ref<t => unit> = ref(ignore)
  Somewhere wanted: ref<(. t) => unit>

  The incompatible parts:
    'a => unit vs (. t) => unit

Is it too early to test with real-world projects?

@cristianoc
Copy link
Collaborator Author

Just got curious and tried the following:

  • download the last PR build
  • install it into one of our projects
  • build in legacy mode => works fine
  • set "uncurried": "always" in bsconfig.json and rebuild

This gave me an error here:

type t
let onEvent: ref<t => unit> = ref(ignore)
 Somewhere wanted: ref<(. t) => unit>

 The incompatible parts:
   'a => unit vs (. t) => unit

Is it too early to test with real-world projects?

This is a good time to start experimenting with projects and see how it goes.
Even though most uses of compiler libs still work, one cannot do magic and have all possible existing code still compile without change.

In this case, ignore, coming from the compiler library, has curried type. And adding it to a reference, gives a reference to a curried function. The annotation, : ref<t => unit>, which is in uncurried-always mode, is interpreted as saying: it's an uncurried type.

In this case you need to wrap let ignore = x => ignore(x) to make ignore an uncurried function.
In general, one could provide a whole special version of the compiler libs, and ignore would be uncurried to begin with. However, that's logistically difficult. So it's interesting to experiment and see how much one needs to manually change (such as re-wrapping ignore in this case).

@cristianoc
Copy link
Collaborator Author

As a side note, one cannot promote automatically a curried function to an uncurried one, as that's not a safe transformation. The opposite one, is safe, and automatic cast in that direction is part of this PR.

@cknitt
Copy link
Member

cknitt commented Feb 6, 2023

Understood. I wonder if we need to provide multiple copies of the standard libraries (compiled in different modes)?

Another thing: In "uncurried always" mode,

let make: ('a => unit) => ('a => unit) = x => x

compiles fine, but gets reformatted to

let make: ('a => unit, 'a) => unit = x => x

which does not compile.

@cristianoc
Copy link
Collaborator Author

Understood. I wonder if we need to provide multiple copies of the standard libraries (compiled in different modes)?

Another thing: In "uncurried always" mode,

let make: ('a => unit) => ('a => unit) = x => x

compiles fine, but gets reformatted to

let make: ('a => unit, 'a) => unit = x => x

which does not compile.

So far reformatting has been architected to only look at the given file, without knowledge of the project it might be in (if any). So it does not really know the project is in uncurried-always mode. There's a command line, but it would need to be supplied manually at the moment bsc -uncurried always -format Foo.res.
Something to look into later.

@cristianoc
Copy link
Collaborator Author

Understood. I wonder if we need to provide multiple copies of the standard libraries (compiled in different modes)?
Another thing: In "uncurried always" mode,

let make: ('a => unit) => ('a => unit) = x => x

compiles fine, but gets reformatted to

let make: ('a => unit, 'a) => unit = x => x

which does not compile.

So far reformatting has been architected to only look at the given file, without knowledge of the project it might be in (if any). So it does not really know the project is in uncurried-always mode. There's a command line, but it would need to be supplied manually at the moment bsc -uncurried always -format Foo.res. Something to look into later.

There's now best-effort support for formatting.

Add support for uncurried-always: a mode where everything is considered uncurried, whether with or without the `.`. This can be turned on with `@@uncurriedAlways` locally in a file. Added a project config `"uncurried"`, which propagates to dependencies, and takes the values: `"legacy"` which changes nothing, or `"default"` for uncurried by default, or `"always"` for uncurried-always.
Using the uncurried-always mode is difficult when calling compiler libraries, as those are uncurried.
This introduces automatic curried application in uncurried-only mode: when a curried function is used in application, the application is treated as curried application.
This means that even in uncurried-always mode, one can use compiler libraries in most cases. Exceptions are functions that take callbacks, such as `Array.map`.
When functions such as `Array.map` are called via automatic curried application, if one of the arguments is a callback, turn that into an uncurried type.
This allows passing an uncurried callback even though the original `Array.map` expects a curried callback.
Instead of using an ad-hoc treatment of function arguments which are callbacks in automatic curried application, use subtyping in uncurried-always mode.
So an uncurried function is cast to a curried one during unification.

In addition to the existing examples, this also supports uses of `|>` and callbacks to `div` such as `onClick`, which now can be uncurried functions.
…ally in a file.

So it behaves just as it does when set via bsconfig.
Project config is not used in the formatter normally.
This attempts a best-effort way to find bsconfig.json and see if the mode is set.
The editor currently calls this on a temp file, so it tries to find bsconfig from the path of `bsc.exe` used, by looking outside node_modules.
A more robust method should be provided later on, but this should be OK to start experimenting.
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.

4 participants