With the new React Native bindings that I am creating (https://github.com/reasonml-community/bs-react-native/tree/master/bs-react-native-next), my goal is to achieve zero cost for these bindings, or as close to zero cost as it gets. Still, I would like to provide type safety for string enums.
For creating new JS objects, [@bs.obj] combined with [@bs.string] does the job fine (BTW, https://github.com/BuckleScript/bucklescript/issues/2827 would be really nice to have, too).
However, there are also cases where I either need to pass an array of an enum type or to return an object with an enum field from a JS API (without explicitly transforming it to some Reason object with a variant field).
For these cases, my current solution is to use an abstract type for the enum and to provide the possible values in the bindings, see e.g. Easing.t in:
https://github.com/reasonml-community/bs-react-native/blob/master/bs-react-native-next/src/apis/Keyboard.re
https://github.com/reasonml-community/bs-react-native/blob/master/bs-react-native-next/src/apis/Keyboard.bs.js
Unfortunately, providing the values for the enum this way is not zero cost.
It would be great to be able to do something like
[@bs.val] external easeIn: t = "\"easeIn\"";
instead, to provide the required constant as an external value of type t. However, this gives the error
Not a valid global name "easeIn", as "easeIn" is not a valid identifier.
Another use case would be the following (binding for React Native Platform):
type os;
[@bs.module "react-native"] [@bs.scope "Platform"] external os: os = "OS";
[@bs.val] external ios: os = "\"ios\"";
[@bs.val] external android: os = "\"android\"";
@bobzhang: Do you know of a solution? Would it be possible to relax the checks for [@bs.val] to allow string or number constants? Or provide something like [@bs.constant] instead?
/cc @rickyvetter @MoOx
Note you don't need external here.
module N : sig
type os = private string
val ios : os
end = struct
type os = string
let ios = "ios"
end
My question is that do you really have tangible benefit inlining those string literals?
@bobzhang Yes, I do not need external to export instances of an abstract type (hiding the type string), but I would need external to create true zero-cost bindings.
This is all about code size, especially when you have a lot of such string constants. In the example of https://github.com/reasonml-community/bs-react-native/blob/master/bs-react-native-next/src/apis/Keyboard.bs.js, this file could be completely empty if it were not for the string constants.
Also, where using the exported string constants, we will end up with less code, too, if they are inlined.
Finally, in the case of the React Native platform binding, it is essential that
if (Platform.os === Platform.ios) {
// iOS specific code
} else if (Platform.os === Platform.android) {
// Android specific code
};
compiles to
import * as ReactNative from "react-native";
if (ReactNative.Platform.OS === "ios") {
// iOS specific code
} else if (ReactNative.Platform.OS === "android") {
// Android specific code
};
(with the string literals "ios" and "android") because the React Native packager recognizes this usage and optimizes the Android code away when compiling for iOS and vice versa.
For the easing module, that you are using another indirection (submodules) makes things complicated.
For the second, it seems valid request due to the limitations of other toolchain.|
because the React Native packager recognizes this usage and optimizes the Android code away when compiling for iOS and vice versa.
Do you have a source for that?
If we are going to support that the user interface would be
let android = "android" [@@inline]
let number = 3 [@@inline]
let os = true [@@inline]
Would only apply to string, integer, boolean literals, do you have other in mind?
I know this from my experience from previous React Native projects. I am actually not sure if it is documented anywhere.
Looking at the Metro Bundler sources, here is the code that inlines (ReactNative.)Platform.OS:
And here are the tests for that:
As far as I understand, the inlining replaces
if (ReactNative.Platform.OS === "ios") {
// iOS specific code
} else if (ReactNative.Platform.OS === "android") {
// Android specific code
};
e.g. on iOS with
if ("ios" === "ios") {
// iOS specific code
} else if ("ios" === "android") {
// Android specific code
};
and in a later optimization step, the iOS branch is kept and the Android branch removed.
Thanks a lot for already experimenting with [@@inline]! 馃憤
For me, string and integer literals are the most important use cases. (If we have integer, we should probably have float, too, though?)
currently support bs.inline for boolean/int/string. The error message is less than ideal, but it is only for library authors, for users, it is still transparent
Most helpful comment
currently support bs.inline for boolean/int/string. The error message is less than ideal, but it is only for library authors, for users, it is still transparent