Can a TypeScript generator function be accurately typed with additional functionality?

Generator functions come with prototype properties that allow for the addition of behavior. The generated generators inherit this behavior. Unfortunately, TypeScript does not seem to recognize this feature, leaving me unsure of how to make it aware.

For instance, let's consider a basic range generator and try to incorporate a map functionality:

/** Generates integers from 0 up to `hi`, incremented by `step`. */
function* range(hi: number, step: number = 1) {
  for (let i = 0; i < hi; i += step) {
    yield i;
  }
}
range.prototype.map = function*<T>(fn: (arg0: number) => T) {
  for (const i of this) {
    yield fn(i);
  }
};

Unfortunately, this doesn't work as expected. Trying something like range(9).map(i => 9 - i) triggers an error stating

Property 'map' does not exist on type 'Generator<number, void, unknown>'.

(The code itself functions properly—it's just the TypeScript typing that falters.)

I find myself daydreaming about a potential syntax to define this behavior manually in case TypeScript fails to infer it automatically. Perhaps something along these lines:

interface of range {
  map<R>(fn: (i: number) => R): Generator<R>;
}

Is there some secret method I have yet to uncover for achieving this? Or is there any plan to integrate such a feature into the language?

Answer №1

When it comes to TypeScript, while it does support adding properties to functions as expandos, there seems to be no built-in way to do the same for generator function prototypes. For classes, you can merge declarations into the instance interface and make modifications to the prototype later. However, this capability is lacking when it comes to generator functions. There is an ongoing issue on microsoft/TypeScript#36086 addressing this concern, but there has not been any progress so far. If you want to advocate for this feature, you can visit the issue, show your support with a thumbs up, and explain why it is important. However, considering the lack of interest thus far, implementation might not happen anytime soon.

If you desire this functionality in TypeScript, you'll have to implement it yourself. Here's one potential method:

function augmentGenerator<T, M, A extends any[]>(gf: (...a: A) => Generator<T>,
    m: M & ThisType<Generator<T> & M>
) {
    Object.assign(gf.prototype, m);
    return gf as (...a: A) => (Generator<T> & M);
}

The concept here is instead of declaring your generator function first and then modifying the prototype afterward, you can use augmentGenerator with a generator function (with type (...a: A) => Generator<T>) and a set of intended prototype changes (of type M). The compiler will then provide a narrowed type as expected ((...a: A) => (Generator<T> & M)).

To provide context when calling augmentGenerator, I am utilizing the ThisType<T> utility type. This helps the compiler recognize that this inside your modifications refers to a suitable generator.

Let's put it into practice:

const range = augmentGenerator(function* (hi: number, step: number = 1) {
    for (let i = 0; i < hi; i += step) {
        yield i;
    }
}, {
    map: function*<T>(fn: (arg0: number) => T) {
        for (const i of this) {
            yield fn(i);
        }
    }
});

The code compiles without issues; upon inspecting the type of range(), we get:

/* const range: (hi: number, step?: number | undefined) =>
   Generator<number> & {
     map: <T>(fn: (arg0: number) => T) => Generator<T>;
   } */

That aligns with your expectations. Consequently, the following works seamlessly at both runtime and compile-time:

for (const i of range(9).map(i => 9 - i)) {
    console.log(i.toFixed(2)); // 9.00, 8.00, etc
}

Playground link to code

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

"Exploring the process of retrieving URL parameters within an activated link using Angular 7 and executing a REST API call from a service

My aim is to retrieve data by utilizing the id field through Get parameters. Below is the URL code in my HTML that redirects to a specific page without triggering the service to fetch the REST API. <a [routerLink]="['/usedCars/detail', list ...

Exploring Mikro-ORM with Ben Awad's Lireddit: Navigating the Process of Running Initial Migrations

Having some trouble following the lireddit tutorial, particularly with the initial mikro-orm migration step. Encountering a similar issue as mentioned in this post. Tried modifying the constructor of the example entity (tried both provided format and the ...

What is the reasoning behind TypeScript's decision to permit implicit downcasting in method parameters?

Consider the following example: interface Vehicle{ mass:number } interface InspectorClass{ inspect(v:Vehicle):void } class Car implements Vehicle{ mass = 2000 wheels = 4 } class Boat implements Vehicle{ mass = 3000 sails = 2 } ...

The readline interface in Node that echoes each character multiple times

After creating a node readline interface for my project, I encountered an unusual issue. this.io = readline.createInterface({ input: process.stdin, output: process.stdout, completer:(line:string) => { //adapted from Node docs ...

The TypeScript optional callback parameter is not compatible with the anonymous function being passed to it

Encountering an issue with TS callbacks and function signatures. Here is my scenario: ... //inside a class //function should accept a callback function as parameter refreshConnection(callback?: Function) { //do something //then ca ...

The jspdf tool tries to cram my extensive data into a single page, resulting in an overcrowded and barely visible PDF document

My PDF generated using Jspdf is being forced to fit in one page, making it difficult to see all the data because there is a huge amount of information present. To view the issue, please visit this link: https://jsfiddle.net/frost000/04qt7gsm/21/ var pdf ...

The ng-bootstrap typeahead is encountering an error: TypeError - Object(...) is not functioning correctly

Hey there! I'm trying to integrate the Angular Bootstrap typeahead component in my Angular 5 application by following the linkToTypeahead. However, I'm encountering some errors along the way. Here's what I'm seeing: ERROR TypeError: Ob ...

When hosting on render.com, the session data is not retained when redirecting to other routes

My login API checks if the user has a saved cookie in MongoDB and saves the value into req.session using the req.session.save() method. Afterward, it redirects to another route to create a response and send the client session data to be used. This function ...

Selecting an option with a specific index in Angular 2 RC2

I have encountered a situation where the select options are non-unique, with the same value representing different things. This is how our data is structured and I need to work within those constraints. <select id="mySelect"> <option value = "1 ...

What is the best way to attach events to buttons using typescript?

Where should I attach events to buttons, input fields, etc.? I want to keep as much JS/jQuery separate from my view as possible. Currently, this is how I approach it: In my view: @Scripts.Render("~/Scripts/Application/Currency/CurrencyExchangeRateCreate ...

Patience is key when waiting for one observable to respond before triggering another in Angular's async environment

What is my goal? I have several components with similar checks and data manipulation tasks. I am looking to centralize these tasks within an observable. To achieve this, I created an observable named "getData" in my service... The complexity lies in the f ...

typescript function intersection types

Encountering challenges with TypeScript, I came across the following simple example: type g = 1 & 2 // never type h = ((x: 1) => 0) & ((x: 2) => 0) // why h not never type i = ((x: 1 & 2) => 0)// why x not never The puzzling part is w ...

What is the process for defining a property type as a textual representation of a Type name in TypeScript?

Consider the following classes and interface: class Foo {} interface Bar {} I am looking to define a type that includes a property with a specified type: type DynamicPropertyName<T> = ... <-- ??? After defining the type, I want to use it like th ...

Explicit final argument in TypeScript

Is it feasible to define a function in TypeScript 2.7.2 and above with variable parameters, but ensuring that the final parameter has a specific type? I am attempting to craft an ambient TypeScript declaration for a JavaScript library that utilizes functi ...

Guide on dynamically applying a CSS rule to an HTML element using programming techniques

Currently working with Angular 6 and Typescript, I am facing a unique challenge. My task involves adding a specific CSS rule to the host of a component that I am currently developing. Unfortunately, applying this rule systematically is not an option. Inste ...

Is it considered an anti-pattern in TypeScript to utilize BehaviorSubject for class or object properties?

When working with Angular, I find myself frequently displaying members of classes in an Angular HTML template. These classes often share common members structured like this: class Foo { bar: string; bas: Date; } There are instances where I need to ...

A guide on accessing a dynamic object key in array.map()

How can I dynamically return an object key in array.map()? Currently, I am retrieving the maximum value from an array using a specific object key with the following code: Math.max.apply(Math, class.map(function (o) { return o.Students; })); In this code ...

Unable to declare a string enum in TypeScript because string is not compatible

enum Animal { animal1 = 'animal1', animal2 = 'animal2', animal3 = 'animal3', animal4 = 'animal4', animal5 = 'animal5' } const species: Animal = 'animal' + num Why does typescr ...

The Interface in TypeScript will not function properly when used on a variable (Object) that has been declared with the value returned from a function

I am currently in the process of developing an application using Ionic v3. Strangely, I am encountering issues with my interface when trying to assign a variable value returned by a function. Here is an example that works without any problems: export int ...

Looking for assistance in correctly identifying types in react-leaflet for TypeScript?

Embarking on my 'first' project involving react-scripts-ts and react-leaflet. I am attempting to create a class that should be fairly straightforward: import {map, TileLayer, Popup, Marker } from 'react-leaflet'; class LeafletMap exte ...