Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 17 additions & 19 deletions apps/docs/markdown-pages/docs/manual/bind-to-js-object.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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`.
2 changes: 2 additions & 0 deletions apps/docs/markdown-pages/docs/manual/polymorphic-variant.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/markdown-pages/docs/manual/type.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,6 @@ export { x };

</CodeTab>

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 `'`.
Expand Down Expand Up @@ -234,6 +232,8 @@ export { payloadResults };

</CodeTab>

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`:
Expand Down