Why don't default parameter values work with an overloaded generator?

1 week ago 10
ARTICLE AD BOX

I figured out how to use overloaded parameter lists with a generator. Google says:

Yes, you can overload a generator function in TypeScript by defining multiple overload signatures without the * symbol and a single implementation signature that includes the *.

How to Overload a Generator Function

TypeScript's function overloading works by defining multiple type signatures (overload signatures) followed by a single implementation that covers all cases. The implementation signature is not callable from outside the function, but is used for type checking within the function body.

For a generator, the key is that the overload signatures should be regular function signatures that return an appropriate iterator type (like IterableIterator<T> or AsyncIterableIterator<T>), while the actual implementation uses the * syntax.

It's an unusual case, but it actually works. However, in my case, the actual implementation function has 3 parameters and I've provided a default value for the third one since it's often not used. Here is my code:

export type TAbortFunc<TIn> = (item: TIn, i: number) => boolean export type TGenFunc<TIn,TOut> = (item: TIn, i: number) => IterableIterator<TOut> export type TAsyncGenFunc<TIn,TOut> = (item: TIn, i: number) => AsyncIterableIterator<TOut> # --------------------------------------------------------------------------- function neverAbort<TIn>(item: TIn, i: number): boolean return false export function mapper<TIn,TOut>( lItems: Iterable<TIn> func: TGenFunc<TIn, TOut> abortFunc: TAbortFunc<TIn> ): IterableIterator<TOut> export function mapper<TIn,TOut>( lItems: AsyncIterable<TIn> func: TGenFunc<TIn,TOut> abortFunc: TAbortFunc<TIn> ): AsyncIterableIterator<TOut> export function* mapper<TIn,TOut>( lItems: Iterable<TIn> | AsyncIterable<TIn> func: TGenFunc<TIn,TOut> abortFunc: TAbortFunc<TIn> = neverAbort<TIn> ): IterableIterator<TOut> | AsyncIterableIterator<TOut> if (Symbol.iterator in lItems) for item,i of lItems if abortFunc item, i return yield* func(item, i) else if (Symbol.asyncIterator in lItems) for await item,i of lItems if abortFunc item, i return yield* func(item, i) else throw new Error("Bad parameters") return

The 3rd parameter is an "abort function". It allows stopping the processing of items in a list (which can be any iterable or async iterable) by returning a true value. I'm trying to provide a default value - a function that always returns false.

As I said, the function works as long as I provide a 3rd parameter in my call to mapper(). For example, this works:

"use strict"; const yielder: TGenFunc<number, number> = function*(n, i) { yield 2*n return } const cb: TAbortFunc<number> = function(n, i) { return false } for await (const item of mapper([1, 2, 3], yielder, cb)) { console.log(item) }

By "works", I mean both that

when type checking, no errors are reported and it prints out the 3 numbers 2, 4 and 6 on separate lines.

However, if I leave out the 3rd parameter in the call to mapper(), i.e. the cb function, I get these errors:

TS2554 [ERROR]: Expected 3 arguments, but got 2. for await (const item of mapper([1, 2, 3], yielder)) { ~~~~~~ at file:///C:/Users/johnd/util/src/temp/temp.ts:13:26 An argument for 'abortFunc' was not provided. abortFunc: TAbortFunc<TIn> ~~~~~~~~~~~~~~~~~~~~~~~~~~ at file:///C:/Users/johnd/util/src/lib/var-free.lib.ts:18:3 error: Type checking failed.

The code actually runs correctly, so my question really has more to do with TypeScript's type checking than the code generated.

I use Deno, so type checking is done with deno check <file path>. However, Deno doesn't do the type checking itself - it defers to TypeScript for that. Here are the versions I'm using:

$ deno --version deno 2.6.5 (stable, release, x86_64-pc-windows-msvc) v8 14.2.231.17-rusty typescript 5.9.2
Read Entire Article