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 ee1255483..aefe3f84f 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,7 @@ 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 **phantom type parameters** and [polymorphic variants](./polymorphic-variant.mdx#lower-bound-). Suppose `animals.js` exports the hierarchy `Animal → Mammal → Dog`: ```js // animals.js @@ -245,49 +245,47 @@ export class Dog extends Mammal { Declare the base type and its methods in `Animal.res`: ```res file=Animal.res -type t +type t<'a> -@send external getName: t => string = "getName" +@send external getName: t<'a> => string = "getName" ``` -In `Mammal.res`, relate the subclass to its parent: +The **phantom type parameter** `'a` tracks the class in the type system without adding data to the JavaScript object. + +In `Mammal.res`, require the `#Mammal` tag while allowing subclasses to add their own tags: ```res file=Mammal.res -type t = private Animal.t +type t<'a> = Animal.t<[> #Mammal] as 'a> ``` -Repeat for `Dog.res`, adding its constructor and methods: +The constraint `[> #Mammal] as 'a` means `'a` must include at least `#Mammal`. + +In `Dog.res`, close the set of tags and add its constructor and methods: ```res file=Dog.res -type t = private Mammal.t +type t = Mammal.t<[#Dog | #Mammal]> @new @module("./animals.js") external make: string => t = "Dog" @send external bark: t => string = "bark" ``` -Use `:>` to **coerce** a subclass instance to a parent type: +Parent methods accept subclass instances directly: ```res let dog = Dog.make("Rex") -let mammal = (dog :> Mammal.t) -let animal = (dog :> Animal.t) -let name = animal->Animal.getName +let name = dog->Animal.getName let sound = dog->Dog.bark ``` -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. +`Animal.getName` accepts any `Animal.t<'a>`, including `Dog.t`. `Dog.bark` accepts only `Dog.t`, so it cannot be called on other animals. -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. - -Private abbreviations describe an existing inheritance relationship; they do not create inheritance or check instances at runtime. +The tags are used only during type checking. The generated JavaScript uses the original class instances. #### Interfaces -If you add a [`.resi` interface](./module.mdx#signatures), expose the same relationship. For example, `Mammal.resi` should contain: +If you add a [`.resi` interface](./module.mdx#signatures), expose the same type parameter and alias so callers can use parent methods. For example, `Mammal.resi` should contain: ```res file=Mammal.resi -type t = private Animal.t +type t<'a> = Animal.t<[> #Mammal] as 'a> ``` - -Exposing only `type t` would hide the relationship and prevent callers from coercing `Mammal.t` to `Animal.t`. diff --git a/apps/docs/markdown-pages/docs/manual/polymorphic-variant.mdx b/apps/docs/markdown-pages/docs/manual/polymorphic-variant.mdx index f19c537e0..cb37f302a 100644 --- a/apps/docs/markdown-pages/docs/manual/polymorphic-variant.mdx +++ b/apps/docs/markdown-pages/docs/manual/polymorphic-variant.mdx @@ -378,6 +378,8 @@ Here, the compiler will enforce the user to define `#Blue | #DeepBlue | #LightBl **Note:** Since we want to define an extensible poly variant, we need to provide a type placeholder `<'a>`, and also add `as 'a` after the poly variant declaration, which essentially means: "Given type `'a` is constraint to the minimum set of constructors (`#Blue | #DeepBlue | #LightBlue`) defined in `basicBlueTone`". +For a practical use of lower bounds with phantom type parameters, see [representing JavaScript class inheritance](./bind-to-js-object.mdx#represent-class-inheritance). + ### Upper Bound `[<` The upper bound works in the opposite way than a lower bound: the extending type may only use constructors that are stated in the upper bound constraint. diff --git a/apps/docs/markdown-pages/docs/manual/type.mdx b/apps/docs/markdown-pages/docs/manual/type.mdx index 205500306..f5f43f342 100644 --- a/apps/docs/markdown-pages/docs/manual/type.mdx +++ b/apps/docs/markdown-pages/docs/manual/type.mdx @@ -113,8 +113,6 @@ 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 `'`. @@ -234,6 +232,8 @@ export { payloadResults }; +Type parameters can also track information used only during type checking. These **phantom types** can model [JavaScript class inheritance](./bind-to-js-object.mdx#represent-class-inheritance) in bindings. + ## Recursive Types Just like a function, a type can reference itself within itself using `rec`: