From 44aade1693bf657fc026ecdacdad57fb47f55016 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Wed, 21 Oct 2026 14:54:23 +0200 Subject: [PATCH 1/3] docs: explain bindings for JavaScript class inheritance Extend the existing class-binding section with a generic Animal, Mammal, and Dog hierarchy. Explain private type abbreviations, explicit coercion, inherited methods, runtime identity, and interface visibility. Link the guide from the type-alias section and relate it briefly to OpenLayers. --- .../docs/manual/bind-to-js-object.mdx | 111 ++++++++++++++++++ apps/docs/markdown-pages/docs/manual/type.mdx | 2 + 2 files changed, 113 insertions(+) diff --git a/apps/docs/markdown-pages/docs/manual/bind-to-js-object.mdx b/apps/docs/markdown-pages/docs/manual/bind-to-js-object.mdx index 9bbabe767..4a5649384 100644 --- a/apps/docs/markdown-pages/docs/manual/bind-to-js-object.mdx +++ b/apps/docs/markdown-pages/docs/manual/bind-to-js-object.mdx @@ -216,3 +216,114 @@ export { myBook }; ``` + +### Represent Class Inheritance + +ReScript does not define classes, but bindings can describe the inheritance relationships between existing JavaScript classes. Give each class its own type, then use a **private type abbreviation** to relate a subclass to its parent. + +For example, suppose a JavaScript module exports these classes: + +```js +// animals.js +export class Animal { + constructor(name) { + this.name = name; + } + + getName() { + return this.name; + } +} + +export class Mammal extends Animal {} + +export class Dog extends Mammal { + bark() { + return "Woof!"; + } +} +``` + +The hierarchy is `Animal → Mammal → Dog`. Start with an abstract type for `Animal` and bind its methods with [`@send`](./bind-to-js-function.mdx#object-method): + +```res file=Animal.res +type t + +@send external getName: t => string = "getName" +``` + +In `Mammal.res`, declare that a mammal can be used as an animal: + +```res file=Mammal.res +type t = private Animal.t +``` + +Repeat the pattern for `Dog`, and bind its constructor and methods: + +```res file=Dog.res +type t = private Mammal.t + +@new @module("./animals.js") external make: string => t = "Dog" +@send external bark: t => string = "bark" +``` + +The constructor still returns `Dog.t`. The private abbreviations let you explicitly **coerce** that value to its parent types with `:>`: + + + +```res +let dog = Dog.make("Rex") +let mammal = (dog :> Mammal.t) +let animal = (dog :> Animal.t) + +let name = animal->Animal.getName +let sound = dog->Dog.bark +``` + +```js +import * as Dog from "./Dog.js"; + +let dog = Dog.make("Rex"); + +let name = dog.getName(); + +let sound = dog.bark(); + +let mammal = dog; + +let animal = dog; + +export { dog, mammal, animal, name, sound }; +``` + + + +Coercion works through the hierarchy, so `Dog.t` can be coerced directly to `Animal.t` without first being assigned to a `Mammal.t` variable. To call an inherited method without keeping another variable, write: + +```res +let name = (Dog.make("Rex") :> Animal.t)->Animal.getName +``` + +The coercion changes only the type used by the compiler. It does not wrap or copy the instance, call a constructor, or change JavaScript's method dispatch. The generated `mammal` and `animal` variables refer to the same JavaScript object as `dog`. + +#### Why Use `private`? + +A regular [type alias](./type.mdx#type-alias), such as `type t = Animal.t`, would make the types interchangeable. `type t = private Animal.t` keeps the subclass distinct while permitting coercion to the parent. + +The compiler requires `:>` when passing a subclass instance to a binding that expects the parent type. A type annotation with `:` does not perform that coercion. + +The reverse conversion is rejected: an arbitrary `Animal.t` cannot be coerced to `Dog.t`, because not every animal is a dog. After a value has been viewed as `Animal.t`, dog-specific methods also require the original `Dog.t` value. Keep that value if you need both APIs. + +These declarations describe an existing JavaScript relationship; they do not create inheritance or check instances at runtime. Only declare a private abbreviation when the JavaScript API guarantees that the subclass can be used as its parent. + +#### Keep the Relationship Visible in Interfaces + +If your binding has a [`.resi` interface](./module.mdx#signatures), expose the private abbreviation there too. For example, `Mammal.resi` should contain: + +```res file=Mammal.resi +type t = private Animal.t +``` + +Exposing only `type t` would hide the relationship from callers, preventing them from coercing `Mammal.t` to `Animal.t`. + +The same approach works for larger libraries. In [OpenLayers](https://openlayers.org/en/latest/apidoc/module-ol_source_Tile-TileSource.html), for example, `TileSource.t` can be declared as `private Source.t`. Bindings for descendants such as [WMTS](https://openlayers.org/en/latest/apidoc/module-ol_source_WMTS-WMTS.html) can expose the relationship to `TileSource.t` without binding every intermediate class. diff --git a/apps/docs/markdown-pages/docs/manual/type.mdx b/apps/docs/markdown-pages/docs/manual/type.mdx index b4dbc50fb..205500306 100644 --- a/apps/docs/markdown-pages/docs/manual/type.mdx +++ b/apps/docs/markdown-pages/docs/manual/type.mdx @@ -113,6 +113,8 @@ export { x }; +For JavaScript bindings, [private type abbreviations](./bind-to-js-object.mdx#represent-class-inheritance) can preserve distinct types while allowing explicit coercion to a parent type, as needed for class inheritance. + ## Type Parameter (Aka Generic) Types can accept parameters, akin to generics in other languages. The parameters' names **need** to start with `'`. From c49924883bbf909e86cbccbe673e086791c07ab3 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Wed, 21 Oct 2026 15:04:53 +0200 Subject: [PATCH 2/3] docs: shorten the class inheritance guide Keep the generic binding hierarchy, one coercion example, and the interface visibility rule. Remove the OpenLayers note, JavaScript implementation and output, and repeated explanations. --- .../docs/manual/bind-to-js-object.mdx | 79 +++---------------- 1 file changed, 11 insertions(+), 68 deletions(-) diff --git a/apps/docs/markdown-pages/docs/manual/bind-to-js-object.mdx b/apps/docs/markdown-pages/docs/manual/bind-to-js-object.mdx index 4a5649384..3627075aa 100644 --- a/apps/docs/markdown-pages/docs/manual/bind-to-js-object.mdx +++ b/apps/docs/markdown-pages/docs/manual/bind-to-js-object.mdx @@ -219,32 +219,9 @@ export { myBook }; ### Represent Class Inheritance -ReScript does not define classes, but bindings can describe the inheritance relationships between existing JavaScript classes. Give each class its own type, then use a **private type abbreviation** to relate a subclass to its parent. +ReScript has no class declarations, but bindings can model existing JavaScript inheritance using **private type abbreviations**. Suppose `animals.js` exports the hierarchy `Animal → Mammal → Dog`. -For example, suppose a JavaScript module exports these classes: - -```js -// animals.js -export class Animal { - constructor(name) { - this.name = name; - } - - getName() { - return this.name; - } -} - -export class Mammal extends Animal {} - -export class Dog extends Mammal { - bark() { - return "Woof!"; - } -} -``` - -The hierarchy is `Animal → Mammal → Dog`. Start with an abstract type for `Animal` and bind its methods with [`@send`](./bind-to-js-function.mdx#object-method): +Declare the base type and its methods in `Animal.res`: ```res file=Animal.res type t @@ -252,13 +229,13 @@ type t @send external getName: t => string = "getName" ``` -In `Mammal.res`, declare that a mammal can be used as an animal: +In `Mammal.res`, relate the subclass to its parent: ```res file=Mammal.res type t = private Animal.t ``` -Repeat the pattern for `Dog`, and bind its constructor and methods: +Repeat for `Dog.res`, adding its constructor and methods: ```res file=Dog.res type t = private Mammal.t @@ -267,9 +244,7 @@ type t = private Mammal.t @send external bark: t => string = "bark" ``` -The constructor still returns `Dog.t`. The private abbreviations let you explicitly **coerce** that value to its parent types with `:>`: - - +Use `:>` to **coerce** a subclass instance to a parent type: ```res let dog = Dog.make("Rex") @@ -280,50 +255,18 @@ let name = animal->Animal.getName let sound = dog->Dog.bark ``` -```js -import * as Dog from "./Dog.js"; - -let dog = Dog.make("Rex"); +Coercion works through the hierarchy: `Dog.t` can be coerced directly to `Animal.t`. It changes only the type; all three variables refer to the same JavaScript object. -let name = dog.getName(); +Unlike a regular [type alias](./type.mdx#type-alias), `private` keeps the types distinct. Coercion requires `:>`; a `:` annotation alone is not enough. The reverse conversion from `Animal.t` to `Dog.t` is rejected, so keep the original `Dog.t` value if you need dog-specific methods. -let sound = dog.bark(); +Private abbreviations describe an existing inheritance relationship; they do not create inheritance or check instances at runtime. -let mammal = dog; +#### Interfaces -let animal = dog; - -export { dog, mammal, animal, name, sound }; -``` - - - -Coercion works through the hierarchy, so `Dog.t` can be coerced directly to `Animal.t` without first being assigned to a `Mammal.t` variable. To call an inherited method without keeping another variable, write: - -```res -let name = (Dog.make("Rex") :> Animal.t)->Animal.getName -``` - -The coercion changes only the type used by the compiler. It does not wrap or copy the instance, call a constructor, or change JavaScript's method dispatch. The generated `mammal` and `animal` variables refer to the same JavaScript object as `dog`. - -#### Why Use `private`? - -A regular [type alias](./type.mdx#type-alias), such as `type t = Animal.t`, would make the types interchangeable. `type t = private Animal.t` keeps the subclass distinct while permitting coercion to the parent. - -The compiler requires `:>` when passing a subclass instance to a binding that expects the parent type. A type annotation with `:` does not perform that coercion. - -The reverse conversion is rejected: an arbitrary `Animal.t` cannot be coerced to `Dog.t`, because not every animal is a dog. After a value has been viewed as `Animal.t`, dog-specific methods also require the original `Dog.t` value. Keep that value if you need both APIs. - -These declarations describe an existing JavaScript relationship; they do not create inheritance or check instances at runtime. Only declare a private abbreviation when the JavaScript API guarantees that the subclass can be used as its parent. - -#### Keep the Relationship Visible in Interfaces - -If your binding has a [`.resi` interface](./module.mdx#signatures), expose the private abbreviation there too. For example, `Mammal.resi` should contain: +If you add a [`.resi` interface](./module.mdx#signatures), expose the same relationship. For example, `Mammal.resi` should contain: ```res file=Mammal.resi type t = private Animal.t ``` -Exposing only `type t` would hide the relationship from callers, preventing them from coercing `Mammal.t` to `Animal.t`. - -The same approach works for larger libraries. In [OpenLayers](https://openlayers.org/en/latest/apidoc/module-ol_source_Tile-TileSource.html), for example, `TileSource.t` can be declared as `private Source.t`. Bindings for descendants such as [WMTS](https://openlayers.org/en/latest/apidoc/module-ol_source_WMTS-WMTS.html) can expose the relationship to `TileSource.t` without binding every intermediate class. +Exposing only `type t` would hide the relationship and prevent callers from coercing `Mammal.t` to `Animal.t`. From 433becbfc64e305c4a95fe07247ad5d3ef9ba368 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Wed, 21 Oct 2026 15:11:36 +0200 Subject: [PATCH 3/3] docs: restore the JavaScript inheritance example Show the Animal, Mammal, and Dog implementations alongside their bindings while retaining the concise explanation and single usage example. --- .../docs/manual/bind-to-js-object.mdx | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/apps/docs/markdown-pages/docs/manual/bind-to-js-object.mdx b/apps/docs/markdown-pages/docs/manual/bind-to-js-object.mdx index 3627075aa..ee1255483 100644 --- a/apps/docs/markdown-pages/docs/manual/bind-to-js-object.mdx +++ b/apps/docs/markdown-pages/docs/manual/bind-to-js-object.mdx @@ -219,7 +219,28 @@ export { myBook }; ### Represent Class Inheritance -ReScript has no class declarations, but bindings can model existing JavaScript inheritance using **private type abbreviations**. Suppose `animals.js` exports the hierarchy `Animal → Mammal → Dog`. +ReScript has no class declarations, but bindings can model existing JavaScript inheritance using **private type abbreviations**. Suppose `animals.js` exports the hierarchy `Animal → Mammal → Dog`: + +```js +// animals.js +export class Animal { + constructor(name) { + this.name = name; + } + + getName() { + return this.name; + } +} + +export class Mammal extends Animal {} + +export class Dog extends Mammal { + bark() { + return "Woof!"; + } +} +``` Declare the base type and its methods in `Animal.res`: