Supported Types
Some types and values in Reason do not map directly to JavaScript and need to
be converted whenever a value crosses the boundary. This document gives an
overview on how genType
's convertion works on different types.
#Int
Reason values e.g. 1
, 2
, 3
are unchanged. So they are exported to JS values of type number
.
#Float
Reason values e.g. 1.0
, 2.0
, 3.0
are unchanged. So they are exported to JS values of type number
.
#String
Reason values e.g. "a"
, "b"
, "c"
are unchanged. So they are exported to JS values of type string
.
#Optionals
Reason values of type e.g. option(int)
, such as None
, Some(0)
, Some(1)
, Some(2)
, are exported to JS values null
, undefined
, 0
, 1
, 2
.
The JS values are unboxed, and null
/undefined
are conflated.
So the option type is exported to JS type null
or undefined
or number
.
#Nullables
Reason values of type e.g. Js.Nullable.t(int)
, such as Js.Nullable.null
, Js.Nullable.undefined
, Js.Nullable.return(0)
, Js.Nullable.return(1)
, Js.Nullable.return(2)
, are exported to JS values null
, undefined
, 0
, 1
, 2
.
The JS values are identical: there is no conversion unless the argument type needs conversion.
#Records
Reason record values of type e.g. {x:int}
such as {x:0}
, {x:1}
, {x:2}
, are exported to JS object values {x:0}
, {x:1}
, {x:2}
. This requires a change of runtime representation from arrays to objects.
So they are exported to JS values of type {x:number}
.
Since records are immutable by default, their fields will be exported to readonly property types in Flow/TS. Mutable fields are specified in Reason by e.g. {mutable mutableField: string}
.
The @genType.as
annotation can be used to change the name of a field on the JS side of things. So e.g. {[@genType.as "y"] x:int}
is exported as JS type {y:int}
.
If one field of the Reason record has option type, this is exported to an optional JS field. So for example Reason type {x: option(int)}
is exported as JS type {x?: number}
.
#Objects
Reason object values of type e.g. {. "x":int}
such as {"x": 0}
, {"x": 1}
, {"x": 2}
, are exported as identical JS object values {x:0}
, {x:1}
, {x:2}
. This requires no conversion. So they are exported to JS values of type {x:number}
.
A conversion is required only when the type of some field requires conversions.
Since objects are immutable by default, their fields will be exported to readonly property types in Flow/TS. Mutable fields are specified in Reason by e.g. {. [@bs.set] "mutableField": string }
.
It is possible to mix object and option types, so for example the Reason type {. "x":int, "y":option(string)}
exports to JS type {x:number, ?y: string}
, requires no conversion, and allows option pattern matching on the Reason side.
Object field names follow bucklescript's mangling convention (so e.g. _type
in Reason represents type
in JS):
Remove trailing "__" if present. Otherwise remove leading "_" when followed by an uppercase letter, or keyword.
#Tuples
Reason tuple values of type e.g. (int, string)
are exported as identical JS values of type [number, string]
. This requires no conversion, unless one of types of the tuple items does.
While the type of Reason tuples is immutable, there's currently no mature enforcement in TS/Flow, so they're currenty exported to mutable tuples.
#Variants
Ordinary variants (with capitalized cases, e.g. | A | B(int)
) and polymorphic variants (with a backtick, e.g. | `A | `B(int)
) are represented in the same way, so there's no difference from the point of view of JavaScript. Polymorphic variants don't have to be capitalized.
Variants can have an unboxed, or a boxed representation. The unboxed representation is used when there is at most one case with a payload, and that payload has object type; otherwise, a boxed representation is used. Object types are arrays, objects, records and tuples.
Variants without payloads are essentially sequences of identifiers.
E.g. type [@genType] type days = | Monday | Tuesday
.
The corresponding JS representation is "Monday"
, "Tuesday"
.
Similarly, polymorphic variant type [@genType] type days = [ | `Monday | `Tuesday ]
has the same JS representation.
When at most one variant case has a payload, and if the payload is of object type, e.g.
[ | Unnamed | Named({. "name": string, "surname": string}) ]
then the representation is unboxed: JS values are e.g. "Unnamed"
and
{name: "hello", surname: "world"}
. Similarly for polymorphic variants.
Note that this unboxed representation does not use the label "Named"
of the variant case with payload, because that value is distinguished from the other payload-less cases by its type: an object.
If there is more than one case with payload, or if the single payload has not type object, a boxed representation is used. The boxed representation has shape {tag: "someTag", value: someValue}
.
For example, type | A | B(int) | C(string)
has values such as "A"
and
{tag: "B", value: 42}
and {tag: "C", value: "hello"}
.
Polymorhphic variants are treated similarly. Notice that payloads for polymorphic variants are always unary: `Pair(int,int)
has a single payload of type (int,int)
. Instead, ordinary variants distinguish between unary Pair((int,int))
and binary Pair(int,int)
payloads. All those cases are represented in JS as {tag: "Pair", value: [3, 4]}
, and the conversion functions take care of the different Reason representations.
The @genType.as
annotation can be used to modify the name emitted for a variant case on the JS side. So e.g. | [@genType.as "Arenamed"] A
exports Reason value A
to JS value "Arenamed"
.
Boolean/integer/float constants can be expressed as | [@genType.as true] True
and | [@genType.as 20] Twenty
and | [@genType.as 0.5] Half
. Similarly for polymorphic variants.
The @genType.as
annotation can also be used on variants with payloads to determine what appears in { tag: ... }
.
For more examples, see Variants.re and VariantsWithPayload.re.
NOTE When exporting/importing values that have polymorphic variant type, you have to use type annotations, and cannot rely on type inference. So instead of let monday = `Monday
, use let monday : days = `Monday
. The former does not work, as the type checker infers a type without annotations.
#Arrays
Arrays with elements of Reason type t
are exported to JS arrays with elements of the corresponding JS type. If a conversion is required, a copy of the array is performed.
Immutable arrays are supported with the additional Reason library
ImmutableArray.re/.rei, which currently needs to be added to your project.
The type ImmutableArray.t(+'a)
is covariant, and is mapped to readonly array types in TS/Flow. As opposed to TS/Flow, ImmutableArray.t
does not allow casting in either direction with normal arrays. Instead, a copy must be performed using fromArray
and toArray
.
#Functions and Function Components
Reason functions are exported as JS functions of the corresponding type.
So for example a Reason function foo : int => int
is exported as a JS function from numbers to numbers.
If named arguments are present in the Reason type, they are grouped and exported as JS objects. For example foo : (~x:int, ~y:int) => int
is exported as a JS function from objects of type {x:number, y:number}
to numbers.
In case of mixed named and unnamed arguments, consecutive named arguments form separate groups. So e.g. foo : (int, ~x:int, ~y:int, int, ~z:int) => int
is exported to a JS function of type (number, {x:number, y:number}, number, {z:number}) => number
.
Function components are exported and imported exactly like normal functions. For example:
RE[@genType]
[@react.component]
let make = (~name) => React.string(name);
For renaming, named arguments follow bucklescript's mangling convention:
Remove trailing "__" if present. Otherwise remove leading "_" when followed by an uppercase letter, or keyword.
For example:
RE[@genType]
let exampleFunction = (~_type) => "type: " ++ _type;
#Record Components
ReasonReact record components (deprecated) with props of Reason types t1
, t2
, t3
are exported as reactjs components with props of the JS types corresponding to t1
, t2
, t3
. The annotation is on the make
function: [@genType] let make ...
.
A file can export many components by defining them in sub-modules. The toplevel component is also exported as default.
#imported types
It's possible to import an existing TS/Flow type as an opaque type in Reason. For example,
RE[@genType.import "./SomeFlowTypes"] type weekday;
defines a type which maps to weekday
in SomeFlowTypes.js
. See for example
Types.re
and SomeFlowTypes.js.
#Recursive Types
Recursive types which do not require a conversion are fully supported. If a recursive type requires a conversion, only a shallow conversion is performed, and a warning comment is included in the output. (The alternative would be to perform an expensive conversion down a data structure of arbitrary size). See for example Types.re.
#First Class Modules
Reason first class modules are converted from their array Reason runtime representation to JS Object types. For example,
REmodule type MT = { let x: int; let y: string; };
module M = { let y = "abc"; let x = 42; };
[@genType] let firstClassModule: module MT = (module M);
is exported as a JS object of type
RE{x: number, y: string}
Notice how the order of elements in the exported JS object is determined by the module type MT
and not the module implementation M
.
#Polymorphic Types
If a Reason type contains a type variable, the corresponding value is not converted. In other words, the conversion is the identity function. For example, a Reason function of type {payload: 'a} => 'a
must treat the value of the payload as a black box, as a consequence of parametric polymorphism. If a typed back-end is used, the reason type is converted to the corresponding generic type.
#exporting values from polymorphic types with hidden type variables
For cases when a value that contains a hidden type variable needs to be converted, a function can be used to produce the appropriate output:
Doesn't work
RE[@genType]
let none = None;
JSexport const none: ?T1 = OptionBS.none; // Errors out as T1 is not defined
Works
RE[@genType]
let none = () => None;
JSconst none = <T1>(a: T1): ?T1 => OptionBS.none;
#Promises
Valiues of type Js.Promise.t(arg)
are exported to JS promises of type Promise<argJS>
where argJS
is the JS type corresponding to arg
.
If a conversion for the argument is required, the conversion functions are chained via .then(promise => ...)
.