Creating two number-like types in TypeScript that are incompatible with each other can be achieved by defining two

I've been grappling with the challenge of establishing two number-like/integer types in TypeScript that are mutually incompatible.

For instance, consider the following code snippet where height and weight are both represented as number-like types. However, combining them or treating them as equivalent is illogical and should result in an error:


type height = number; // inches
type weight = number; // pounds
var a: height = 68;
var b: weight = operateScale(); // Call to external function returning weight (non-TypeScript).
console.log(a+b); // This should produce an error.

Is there a method to define two numeric types that are distinct from each other?


EDIT: As noted in the comments, this behavior resembles Haskell's newtype functionality.


EDIT2: After spending hours troubleshooting the issue, I finally arrived at a solution which I have shared below.

Answer №1

In TypeScript, the concept closest to a newtype is to create a new kind of "nominal" type since TypeScript lacks nominal types. One workaround is to use branding techniques where you can create distinct types by adding a label to them.

interface Height { 
  __brand: "height"
}
function height(inches: number): Height {
  return inches as any;
}
function inches(height: Height): number {
  return height as any;
}

interface Weight { 
  __brand: "weight"
}
function weight(pounds: number): Weight {
  return pounds as any;
}
function pounds(weight: Weight): number {
  return weight as any;
}

const h = height(12); // one foot
const w = weight(2000); // one ton

These new types like Height and

Weight</code are treated as separate entities by the compiler. Functions like <code>height()
, inches(), weight(), and pounds() act as constructors and accessors for these types with runtime implementations using type assertions.

const h = 12 as any as Height;
const w = 2000 as any as Weight;

By defining distinct named types, you can ensure that you don't mix up different types unintentionally in your code. However, these new types are not interchangeable with regular number types, just like with newtype. To enforce this distinction, custom functions such as add() can be used:

type Dimension = Height | Weight;

function add<D extends Dimension>(a: D, b: D): D {
  return ((a as any) + (b as any)) as any;
}

The add() function ensures that only values of the same type can be added together. Overloads can also be used for stricter type checking if needed.

const twoH = add(h, h); // twoH is a Height
const twoW = add(w, w); // twoW is a Weight
const blah = add(h, w); // error, Height and Weight don't mix

To integrate these new types into external functions, specify the expected return type explicitly:

declare function measureScale(): Weight;

var a = height(68);
var b = measureScale();

By enforcing correct type usage, compile-time errors can be caught early and prevent unintended type mixing.

console.log(add(a,b)); // err
console.log(add(a,a)); // okay

Here's a playground link with the code examples provided above.

Answer №2

After spending countless hours trying to figure it out, I finally came up with a solution:

class length extends Number {}
class width extends Number {}

By extending the Number class, Typescript provides the ability to create unique numeric data types.

You can then proceed to use the variables as demonstrated above.

var x: length = 35;
var y: width = 70;
console.log(x+y); // This should result in an error.

The problem arises when attempting this operation:

console.log(x+x); // This should NOT produce an error.

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

Jasmine encountered an error while trying to compare the same string: 'Expected the values to match.'

I'm encountering an error message, despite verifying that the strings are identical: Expected { $$state : { status : 1, value : { customerNumber : 'customerNumber', name : 'name', userId : 'buId', customerType : 'ty ...

A function in Typescript that dynamically determines its return type based on a specified generic parameter

Currently, I am attempting to create a function where the return type is determined by a generic argument. Let me share a code snippet to illustrate: type ABCDE = 'a' | 'b'; function newFunc<U extends ABCDE>(input: U): U extends ...

Guide to building a nested React component

My custom dropdown component requires 2 props: trigger (to activate the dropdown) list (content to display in the dropdown) Below is the implementation of my component: import { useLayer } from "react-laag"; import { ReactElement, useState } fr ...

How can you limit a type reference to a specific file in TypeScript?

Currently, I am working on writing universal JavaScript code that can be used in both Node and browser environments. While most of the code works independent of the environment, there are certain parts where different implementations are required based on ...

NestJs encountering issues with reading environment variables

I used the instructions from the Nest documentation to set up the configuration, however, it's not functioning correctly. app.module.ts @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true }), TypeOrmModule.forRoot(config), AuthMo ...

What is the best way to retrieve HTML content using an Angular method?

Okay, so the title might not be the greatest...but I couldn't think of anything better: I want to emphasize search keywords in the result list...that's why I'm having trouble with this problem. CSS: .highlightText{ font-weight: bold; } In ...

Ways to mandate a field to only be of type object in TypeScript

I need to design a type that includes one mandatory property and allows for any additional properties. For instance, I require all objects to have an _id property of type string. {_id: "123"} // This will meet the criteria {a: 1} // This will not work as i ...

Looking to reallocate information, paginate, and sort each time a new row is added to the mat-table

In my application, I have a customized <mat-table> with an implemented addRow() function that adds a new row to the table using default values based on the data type. The challenge I'm facing is that each time a new row is added, I find myself ...

Accessing Webpack bundles using an "@" symbol for imports

I am currently working on bundling a Node Express server that was created using TypeScript and is being packaged with Webpack. Everything seems to be running smoothly when I compile/transpile the code into one JavaScript file called server.js. However, af ...

Menu with options labeled using IDs in FluentUI/react-northstar

I'm currently working on creating a dropdown menu using the FluentUI/react-northstar Dropdown component. The issue I'm facing is that the 'items' prop for this component only accepts a 'string[]' for the names to be displayed ...

Ways to dynamically configure Angular form data

Below is an Angular form group that I need help with. My goal is to initialize the form and if there is no data coming into the Input() data property, then set the form values as empty strings '' for user input. However, if there is indeed form d ...

The NextJS API route functions flawlessly when tested locally, generating over 200 records. However, upon deployment to Vercel, the functionality seems to

Here is the API route that I am using: https://i.stack.imgur.com/OXaEx.png Below is the code snippet: import type { NextApiRequest, NextApiResponse } from "next"; import axios from "axios"; import prisma from "../../../lib/prisma ...

:id Path replaces existing routes

My route configuration looks like this: const routes: Routes = [ { path: '', component: UserComponent, children: [ { path: '', component: LoginComponent }, { path: 'signup', component: SignupComponent } ]}, ...

Typescript struggling to comprehend the conditional rendering flow

I am facing an issue with the code snippet below: import * as React from 'react' type ComponentConfig = [true, {name: string}] | [false, null] const useComponent = (check: boolean): ComponentConfig => { if (check) { return [true, {name ...

Supply type Parameters<T> to a function with a variable number of arguments

I am interested in utilizing the following function: declare function foo(...args: any): Promise<string>; The function foo is a pre-defined javascript 3rd party API call that can accept a wide range of parameters. The objective is to present a coll ...

Utilizing variables for Protractor command line parameters

I am struggling to make variables work when passing parameters as a string in my code. Conf.ts params: { testEnvironment: TestEnvironment.Prod, }, env.ts export enum TestEnvironment { Dev = 'dev', QA = 'qa', Prod ...

Unlock the Full Potential of TypeScript: Seamless Export of Classes and Functions

In my AngularJS project, I have a separate JavaScript file where I declare prototype functions. Here's an example: function lastConv(){ this.item1="2" this.message="hello" ...... } lastConv.prototype.setupfromThread(data) { this.currentBox = dat ...

Exploring Function Overriding in TypeScript

Currently, I'm working on developing a TypeScript method. import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable({ p ...

Using renderProps in combination with TypeScript

I've encountered an issue while trying to convert my React project to TypeScript, specifically with the login component that uses react-google-login. The error I'm facing is related to renderProps: Overload 1 of 2, '(props: { component: El ...

Exploring Angular 5's *ngFor directive with an array of objects

Here is the data I am working with: Initial set of data: var input = [ {ru: "R201", area: "211", unit: "211"}, {ru: "R201", area: "212", unit: "NONE"}, {ru: "R201", area: "HCC", unit: "NONE"}]; Desired result data: var result = [ {area: ...