TIL: Determining output types from input

TypeScript lacks (useful) function overloading. This makes sense — TypeScript largely has zero runtime representation. Function overloading is generally done by having multiple functions with the same name and different type signature. For example, in Java:

void doThing(int a) {
    System.out.println("Got an int!");
}

void doThing(String b) {
    System.out.println("Got a string!");
}

void main {
    doThing(1); // Got an int!
    doThing("1"); // Got a string!
}

The correct function will be called depending on the type passed in.

TypeScript is largely erased at runtime, so there isn’t a way to determine which function implementation should be called. For example, consider this hypothetical syntax:

function doThing(a: numbera: number) {
Duplicate function implementation.
var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v22.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v22.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
("Got an number!");
} function doThing(b: stringb: string) {
Duplicate function implementation.
var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v22.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v22.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
("Got a string!");
} function doThing(a: number): voiddoThing(1); function doThing(a: number): voiddoThing("1");
Argument of type 'string' is not assignable to parameter of type 'number'.

At runtime, do we want to call doThing(number) or doThing(string)?

JavaScript is weakly-typed, so it can’t know if you’re wanting it to type coerce or not. In this example a straightforward solution would be to call the strongest match, but it becomes more complicated when you consider more complex types. For example:

type 
type Person = {
    name: string;
    age: number;
}
Person
= {
name: stringname: string; age: numberage: number; }; type
type Dog = {
    name: string;
    weight: string;
}
Dog
= {
name: stringname: string; weight: stringweight: string; }; function doThing(a: Persona:
type Person = {
    name: string;
    age: number;
}
Person
) {
Duplicate function implementation.
var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v22.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v22.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
("Got a person!");
} function doThing(b: Dogb:
type Dog = {
    name: string;
    weight: string;
}
Dog
) {
Duplicate function implementation.
var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v22.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v22.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
("Got a dog!");
} function doThing(a: Person): voiddoThing({ name: stringname: "John", age: numberage: 21, weight: 140,
Object literal may only specify known properties, and 'weight' does not exist in type 'Person'.
});

There aren’t a reasonable set of rules to determine which function to call. So, what can we do instead?

First, it’s worth mentioning that TypeScript does kinda have function overloads. I haven’t had a good experience using them, so I never use them.

Instead, I usually reach for conditional types. This lets me do something that kinda looks like function overloading. It allows me to write one function that handles multiple cases, and can even allow me to determine the output type based on the input.

For example:

type 
type BoardGame = {
    name: string;
    numberOfPieces: number;
}
BoardGame
= {
name: stringname: string; numberOfPieces: numbernumberOfPieces: number; }; type
type VideoGame = {
    name: string;
    platform: "PC" | "Console";
}
VideoGame
= {
name: stringname: string; platform: "PC" | "Console"platform: "PC" | "Console"; }; type type Game = BoardGame | VideoGameGame =
type BoardGame = {
    name: string;
    numberOfPieces: number;
}
BoardGame
|
type VideoGame = {
    name: string;
    platform: "PC" | "Console";
}
VideoGame
;
type type BoardGamePieces<G extends Game> = G extends BoardGame ? number : undefinedBoardGamePieces<function (type parameter) G in type BoardGamePieces<G extends Game>G extends type Game = BoardGame | VideoGameGame> = function (type parameter) G in type BoardGamePieces<G extends Game>G extends
type BoardGame = {
    name: string;
    numberOfPieces: number;
}
BoardGame
? number : undefined;
function function getNumberOfPieces<G extends Game>(game: G): BoardGamePieces<G>getNumberOfPieces<function (type parameter) G in getNumberOfPieces<G extends Game>(game: G): BoardGamePieces<G>G extends type Game = BoardGame | VideoGameGame>(game: G extends Gamegame: function (type parameter) G in getNumberOfPieces<G extends Game>(game: G): BoardGamePieces<G>G): type BoardGamePieces<G extends Game> = G extends BoardGame ? number : undefinedBoardGamePieces<function (type parameter) G in getNumberOfPieces<G extends Game>(game: G): BoardGamePieces<G>G> { if ("numberOfPieces" in game: G extends Gamegame) { return game: BoardGamegame.numberOfPieces: numbernumberOfPieces as type BoardGamePieces<G extends Game> = G extends BoardGame ? number : undefinedBoardGamePieces<function (type parameter) G in getNumberOfPieces<G extends Game>(game: G): BoardGamePieces<G>G>; } return var undefinedundefined as type BoardGamePieces<G extends Game> = G extends BoardGame ? number : undefinedBoardGamePieces<function (type parameter) G in getNumberOfPieces<G extends Game>(game: G): BoardGamePieces<G>G>; } const const result1: numberresult1 =
function getNumberOfPieces<{
    name: string;
    numberOfPieces: number;
}>(game: {
    name: string;
    numberOfPieces: number;
}): number
getNumberOfPieces
({
name: stringname: "Catan", numberOfPieces: numbernumberOfPieces: 100, }); const const result2: undefinedresult2 =
function getNumberOfPieces<{
    name: string;
    platform: "PC";
}>(game: {
    name: string;
    platform: "PC";
}): undefined
getNumberOfPieces
({
name: stringname: "Minecraft", platform: "PC"platform: "PC", });

Recent posts from blogs that I like

Paintings of the Franco-Prussian War: 2 The Siege of Paris

As winter grew colder, Parisians started to starve. A city known for its food and restaurants had to scavenge meals based on horse, dog, cat and even rat.

via The Eclectic Light Company

Impromptu disaster recovery

via fasterthanlime

Notes on implementing Attention

Some notes on implementing attention blocks in pure Python + Numpy. The focus here is on the exact implementation in code, explaining all the shapes throughout the process. The motivation for why attention works is not covered here - there are plenty of excellent online resources explaining it. Seve...

via Eli Bendersky