Compiler ยท Mar 26, 2020

Generalized Uncurry Support in 7.3

Hongbo Zhang
Compiler Team

Introduction

ReasonML is a curried language, while Js is an uncurried language. When compiling ReasonML into Js, there's lots of headache due to the semantics mismatch.

After several years of research and development, we will finally reach a new milestone in our next release: adding a lightweight uncurried calling convention to ReasonML.

Why we need native uncurried calling convention

The curried call is inherently slower than the uncurried call.

A native implementation of curried call like purescript does will generate very slow code:

JS
let curriedFunction = x => y => z => x + y +z ; let curriedApply = curriedFunction(1)(2)(3); // memory allocation triggered

BuckleScript does tons of optimizations and very aggressive arity inference so that the curried function is compiled into a multiple-arity function, and when the application is supplied with the exact arguments -- which is true in most cases, it is applied like normal functions.

However, such optimization does not apply to high order functions:

RE
let highOrder = (f,a,b)=> f (a, b) // can not infer the arity of `f` since we know // nothing about the arity of `f`, unless // we do the whole program optimization

In cases where arity inference does not help, the arity guessing has to be delayed into the runtime.

Bindings to JS world:

When we create bindings for high order functions in the JS world, we would like to have native uncurried functions which behave the same as JS world -- no semantics mismatch.

Generalized uncurried calling convention in this release

Before release 7.3, we had introduced uncurried calling convention, however, it has serious limitations -- uncurried functions can not be polymorphic, it does not support labels, the error message leaks the underlying encoding -- now all those limitations are gone!

Previously:

The error messages above are cryptic and hard to understand. And the limitation of not supporting recursive functions make uncurried support pretty weak.

Now those limitations are all gone, you can have polymorphic uncurried recursive functions and it support labels.

The error message is also enhanced significantly

When the uncurried function is used in curried

RE
let add = (. x, y ) => x + y; let u = add (1, 2)

The old error message:

Error: This expression has type (. int, int) => int This is not a function; it cannot be applied.

The new error message

Error: This function has uncurried type, it needs to be applied in ucurried style

When the curried function is used in the uncurried context

RE
let add = ( x, y ) => x + y; let u = add (.1, 2)

The old error message:

Error: This expression has type (int, int) => int but an expression was expected of type (. 'a, 'b) => 'c

The new error message:

Error: This function is a curried function where an uncurried function is expected

When arity mismatch

RE
let add = (. x, y ) => x + y; let u = add (.1, 2,3)

The old message:

Error: This expression has type (. int, int) => int but an expression was expected of type (. 'a, 'b, 'c) => 'd These two variant types have no intersection

The new message:

Error: This function has arity2 but was expected arity3

Note the generalized uncurry support also applies to objects, so that you can use obj##meth (~label1=a,~label2=b).

The only thing where the uncurried call is not supported is optional arguments, if users are mostly targeting JS runtime, we suggest you can try uncurry by default and would like to hear your feedback!

You can already test it today by npm install bs-platform@7.3.0-dev.1 (Windows support will be coming soon).

Want to read more?
Back to Overview