What are the best practices for integrating phantom types with methods in TypeScript?

Take a look at the code snippet below which utilizes phantom types:

const strlen = (str: string) => str.length;

type Const<A, B> = { type: 'Const', value: A };

const Const = <A, B = never>(value: A): Const<A, B> => ({ type: 'Const', value });

const map = <A, B, C>(_f: (value: B) => C, { value }: Const<A, B>): Const<A, C> => Const(value);

const contramap = <A, B, C>(_f: (value: C) => B, { value }: Const<A, B>): Const<A, C> => Const(value);

const constant = Const(true);

map(strlen, constant); // works

contramap(strlen, constant); // works

Playground

The program above successfully passes type checking due to the correct inference of types. The inferred type of the `constant` value is `Const`. When calling the `map` function, the types are used as follows: `A = boolean`, `B = string`, and `C = number`. Similarly, when calling the `contramap` function, the types are `A = boolean`, `B = number`, and `C = string`.

However, I attempted to rewrite the expressions using methods instead of functions for convenience:

const strlen = (str: string) => str.length;

interface Const<A, B> {
    map: <C>(f: (value: B) => C) => Const<A, C>;
    contramap: <C>(f: (value: C) => B) => Const<A, C>;
}

const Const = <A, B = never>(value: A): Const<A, B> => ({
    map: () => Const(value),
    contramap: () => Const(value)
});

const constant = Const(true);

constant.map(strlen); // works

constant.contramap(strlen); // error

Playground

As observed, the `map` method functions correctly, but the `contramap` method encounters an error. This discrepancy arises from the type of `constant` being `Const` and not being refined by the method call. For `map`, the type does not refine to `Const`, while for `contramap`, it doesn't refine to `Const`.

Due to this limitation, either `map` or `contramap` can work individually, but not both. If the object's type is `Const`, `contramap` won't function. On the other hand, if the object's type is `Const`, then `map` will fail to work.

Is there a way to enable both `map` and `contramap` methods to function effectively instead of just one?

Answer №1

This particular issue was resolved by defining the type parameter B as a phantom type within the Const interface.

const strlen = (str: string) => str.length;

interface Const<A, B> {
    map: <B, C>(f: (value: B) => C) => Const<<A, C>;
    contramap: <B, C>(f: (value: C) => B) => Const<<A, C>;
}

const Const = <A, B = never>(value: A): Const<A, B> => ({
    map: () => Const(value),
    contramap: () => Const(value)
});

const constant = Const(true);

constant.map(strlen); // successful

constant.contramap(strlen); // successful

Playground

The shadowing of the type parameter B in the map and contramap functions for the Const interface clarified that it is intended to be a phantom type. As such, it is not meant to be used directly. However, users of the map and contramap functions are given the flexibility to specify the desired type instantiation for B.

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

Tips for utilizing generated *.d.ts files

I have been utilizing a Visual Studio 2017 extension called TypeScript Definition Generator to automatically create TypeScript interfaces for my MVC-ViewModels. Despite trying various similar tools, they all seem to result in the same output (*.cs.d.ts-Fil ...

Having trouble opening a modal view from an action sheet button

Currently, I am in the process of working on a school project and facing an issue where I am trying to open a modal view from an action-sheet button. However, I encounter the following error message: TypeError: Cannot read property 'addMedicationModal ...

Step-by-step guide to initializing a project using React with Typescript and a functional server-side script

I am working on a project that involves a React Typescript app (created using Create React App). In this project, I need to have an executable script that can run alongside the React app. Both the app and the script are intended to only run on local machin ...

Error: Identifier 'LibraryManagedAttributes' is already in use

I am facing a similar issue to: React typescript (2312,14): Duplicate identifier 'LibraryManagedAttributes' and TypeScript error: Duplicate identifier 'LibraryManagedAttributes' Despite upgrading to the latest node/npm/yarn/typescript v ...

The AOT Compilation error occurs in Angular2 RC6 when trying to call the function RouterModule.forChild(ROUTES) which is not supported

Operating Environment: Windows 10, IntelliJ 2016.2, node Angular Version: 2.0.0-rc.6 Language: [all | TypeScript X.X | ES6/7 | ES5] Typescript ES6 Node (for Ahead of Time Compilation issues): node --version = Node 4.4.7, NPM 3.10.6 The AOT com ...

How about: "Are you utilizing the recoil selector for sorting through component

After diving into numerous examples, documentation, and videos about using selectors with recoil, I still can't seem to wrap my head around it. Maybe it's just one of those off days because it shouldn't be this difficult. Here's where ...

Is there a way to retrieve all values in the pathname from a URL after the initial slash

Extracting pathname pathname: "/kids/dlya-malyshey/platya-i-yubki" By using the substr function, I will remove the initial slash location.pathname.substr(1,); Now, the result is kids/dlya-malyshey/platya-i-yubki The challenge is to extract all ...

Developing accessible data in TypeScript

Recently, I've been developing a compact "library" for handling Http requests in TypeScript. The main component of this library is the Http class. After compiling my code into an es5 bundle using gulp-tsify, I wanted to ensure that the class could be ...

Learn how to easily toggle table column text visibility with a simple click

I have a working Angular 9 application where I've implemented a custom table to showcase the data. Upon clicking on a column, it triggers a custom modal dialog. The unique feature of my setup is that multiple dialog modals can be opened simultaneously ...

Error in Angular 6: Unable to access the 'length' property of an undefined variable

Encountering a peculiar issue here. I am using @Input to send data from the parent component to the child component, and even though I can retrieve the correct value in ngOnInit, I still receive an error message stating that the value is undefined. The dat ...

Encountering errors when examining local variables during unit testing on an Angular component

If we have 2 components, namely AppComponent and TestComponent. The TestComponent is being called using its directive in the HTML template of the AppComponent. Within the TestComponent, there is an @Input() property named myTitle. Unit testing is being pe ...

Guidelines for securing login access where the "IsApproved" field must be true before authorization

During the account registration process, I initially set the default value to false for the field IsApproved. I need to create security rules that allow login only for users with IsApproved:true, and redirect those with IsApproved:false to the accessdenied ...

Utilizing Jest to Simulate a Class - Incorporation via Imported Module

I'm having difficulty mocking a constructor of a class from an imported module. Interestingly, it works perfectly when my mock implementation is directly inserted into the jest.mock() factory function, but fails when the implementation is imported fro ...

What is the best way to ensure that Jest waits for an event to occur before performing an assertion?

I've developed a facade for the nats streaming lib in the following way: import nats, { Message, Stan, Subscription, SubscriptionOptions } from 'node-nats-streaming' class NatsHelper { private client: Stan | null = null public connect( ...

I'm having trouble retrieving the information as it is showing as undefined. Can anyone offer any advice?

Attempting to extract specific information from an API response has proven challenging. Despite my efforts to isolate the desired data, all listed details appear as undefined. import { HttpClient } from '@angular/common/http'; import { Injectable ...

Utilizing Custom HTTP Headers in Angular 2 to Enhance Request Handling

Within my Angular 2 project, I am implementing the use of Http (@angular/http) to communicate with my API. In order for these requests to be successful, specific headers, including a JWT header, must be included in each API request. My goal is to have an ...

Exploring Node Stream.Writable Extension in Typescript 4.8

I'm attempting to craft a basic class that implements Node stream.Writable, but it seems like I can't quite grasp the correct syntax - the compiler keeps throwing errors: https://i.stack.imgur.com/UT5Mt.png https://i.stack.imgur.com/Z81eX.png ...

Guide to customizing CSS styles within a div element using TypeScript code in a Leaflet legend

I'm struggling to add a legend to my map using Angular 5 and typescript. I need help with setting CSS styles for the values (grades) that are displayed on the legend. Can someone guide me on where to put the styles? TS: createLegend() { let lege ...

Unlock the Power of Typography in React with Material UI Styled Components

Exploring the world of material UI is a new experience for me. I am currently in the process of creating a styled component using Typography. Below is what I have attempted so far: import styled from 'styled-components'; import { FormGroup, ...

What could be the reason for the crash caused by ngModel?

The usage of [(ngModel)] within a *ngFor-Loop is causing an endless loop and crashing the browser. This is how my HTML looks: <div class="container"> <div class="row" *ngFor="let item of controlSystemTargetViewModel.values; let index = i ...