Typescript's Approach to Currying

In TypeScript, I am attempting to define types for a currying function. The implementation in JavaScript is shown below:

function curry1(fn) {
  return (x) => (fn.length === 1 ? fn(x) : curry1(fn.bind(undefined, x)));
}

This function works effectively by producing a unary function when applied with curry1. Subsequent calls with arguments will result in more unary functions until all parameters are provided, ultimately producing a final result. Currying f1 generates a function equivalent to (a)=>(b)=>(c)=>result.

const make3 = (a, b, c) => `${a}:${b}:${c}`;

const f1 = curry1(make3);
const f2 = f1('A');
const f3 = f2(2);
const f4 = f3('Z');

console.log(f4);  // A:2:Z

I have created a generic type CURRY1 that either produces a result or a curried function based on the number of arguments provided.

type CURRY1<P extends any[], R> = P extends [infer H]
  ? (arg: H) => R // only 1 arg?
  : P extends [infer H0, infer H1, ...infer T] // 2 or more args?
  ? (arg: H0) => CURRY1<[H1, ...T], R>
  : never;

Then, I implemented the actual currying function like so:

function curry1<P extends any[], R>(fn: (...args: P) => R): CURRY1<P, R> {
  return (x: any): any => (fn.length === 1 ? fn(x) : curry1(fn.bind(undefined, x)));
}

When defining a sample function and applying curry1, the correct type inference is displayed by VSCode. However, TypeScript throws an error stating

Type '(x: any) => any' is not assignable to type 'CURRY1<P, R>' ts(2322)
. Using // @ts-ignore before return resolves the issue, but I am seeking a cleaner solution to avoid this error. Any suggestions?

Answer №1

Let's take a look at how I've refactored your type definitions:

interface Curry1<AH, AT extends any[], R> {
  (firstArg: AH): AT extends [infer ATH, ...infer ATT] ? Curry1<ATH, ATT, R> : R;
}

declare function curry1<AH, AT extends any[], R>(
  fn: (firstArg: AH, ...restArgs: AT) => R
): Curry1<AH, AT, R>;

While this version maintains the same functionality as your original code, it enforces having at least one argument in every function call by introducing a first argument of type AH.


The compiler currently struggles to validate the implementation of the curry1 function due to its dependency on generic types AH, AT, and

R</code within a conditional type.</p>
<p>Although the condition <code>fn.length === 1
theoretically could refine the value of fn in the presence of control flow analysis, TypeScript does not implement this behavior, leaving type parameters like
AT</code unaffected. As a result, constraints cannot be reapplied to these type parameters based on function length conditions.</p>
<p>An enhancement request has been filed at <a href="https://github.com/microsoft/TypeScript/issues/33912">microsoft/TypeScript#33912</a> for improved support in this area. Until then, the most viable approach is akin to performing a type assertion inside the implementation to bypass compilation errors.</p>
<hr />
<p>In scenarios similar to this, employing an overloaded function can aid in generating strict typing for call signatures while allowing the developer greater flexibility during implementation. This method permits a clearer separation between caller concerns and implementation tasks, ensuring both receive proper attention without burdening either party with excessive requirements.</p>
<p>To apply this technique, you would structure your code as follows:</p>
<pre><code>// Strongly-typed call signature
function curry1<AH, AT extends any[], R>(
  fn: (firstArg: AH, ...restArgs: AT) => R
): Curry1<AH, AT, R>;

// Loosely-typed implementation signature
function curry1(fn: (...args: any) => any) {
  return (firstArg: any) => (fn.length === 1 ? fn(firstArg) : curry1(fn.bind(undefined, firstArg)));
}

Throughout the implementation, note that liberal use of any signifies instances where explicit checks are relinquished in favor of proceeding unhindered. Consequently, close scrutiny is essential during coding activities to prevent unchecked issues from seeping through.


Now, let's put our adjustments to the test:

const make3 = (a: string, b: number, c: string) => `${a}:${b}:${c}`;    
const f1 = curry1(make3);
const f2 = f1('A');
const f3 = f2(2);
const f4 = f3('Z');    
console.log(f4);  // A:2:Z

The desired behavior is observed seamlessly since it aligns with defined call signatures.

For cases demanding heightened safety assurance, consider incorporating a distinct "function invocation" step into your currying process. Such modifications fall beyond the scope of the current query but are pivotal when augmenting security measures.


Playground link to modified 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

Using Angular (along with Typescript) to showcase JSON data

I previously shared this query, but unfortunately, I didn't receive many helpful responses I have a JSON file that holds the following dataset: [{ "ID": 1030980, "Component": "Glikoza (Gluk)", "Result": "16", "Date": "20.10.2018" } ...

Steps to link two mat-autocomplete components based on the input change of one another

After reviewing the content on the official document at https://material.angular.io/components/autocomplete/examples I have implemented Autocomplete in my code based on the example provided. However, I need additional functionality beyond simple integrati ...

Encountering a host configuration issue while trying to use next/image in a TypeScript environment

I understand that when using Next.js image components without TypeScript, the URL must be configured in next.config.js, but I'm unsure why this doesn't work with TypeScript. ..., is not set up under images in your next.config.js. Learn more her ...

What steps are involved in setting up a sorting feature?

In order to utilize the array.sort() function, a number-returning function must be specified. Typically, it would look something like this: myArray.sort((item1, item2) => a < b); However, I am aiming for a different structure: myArray.sort(by(obj ...

What is the purpose of using detectChanges() when utilizing the default change detection strategy in Angular?

Currently, I am facing an issue while working on my Angular 4 application. I have noticed that I need to use this.changeDetectorRef.detectChanges(); to update the view whenever there is a change in the model. This requirement arises in scenarios like pagin ...

Encountering NoResourceAdapterError when using @admin-bro/nestjs alongside @admin-bro/sequelize and MySQL?

Encountering a similar issue with '@admin-bro/sequelize' NoResourceAdapterError: No compatible adapters found for the provided resource import { Database, Resource } from '@admin-bro/sequelize'; import { AdminModule } from '@admin- ...

Typescript: issue with type guard functionality not functioning properly

type VEvent = { type: "VEVENT"; summary: string; }; type VCalendar = { type: "VCALENDAR"; }; const data: Record<string, VEvent | VCalendar> = (...); array.map((id) => { return { id, title: dat ...

Issue encountered in TypeScript: Property 'counter' is not found in the specified type '{}'.ts

Hey there, I'm currently facing an issue while trying to convert a working JavaScript example to TypeScript (tsx). The error message I keep encountering is: Property 'counter' does not exist on type '{}'.ts at several locations wh ...

I am excited to incorporate superagent into my TypeScript-enabled React project

Currently, I am attempting to integrate superagent into my TypeScript-ed React project. I have been following a tutorial on TypeScript with React and encountered some difficulties when using superagent for server requests, despite successfully utilizing ma ...

Issues have arisen with compiling TypeScript due to the absence of the 'tsc command not found' error

Attempting to set up TypeScript, I ran the command: npm install -g typescript After running tsc --version, it returned 'tsc command not found'. Despite trying various solutions from Stack Overflow, GitHub, and other sources, I am unable to reso ...

How can I dynamically append content to the DOM when a user clicks?

On my form, I have an input field that allows users to click an add button and a new input field will appear below it, with the option to repeat this process indefinitely. However, I am facing an issue with adding an input field to the DOM upon a click eve ...

hide elements only when there is no string to display in Angular2/Typescript

As I experiment with my javascript/typescript code, I've encountered an issue where a string is displayed letter by letter perfectly fine. However, once the entire string is shown, the element containing it disappears and allows the bottom element to ...

Passing data and events between components in React

I'm currently working on developing a dashboard app that includes a basic AppBar and a drawer. I based my design on this Demo. https://codesandbox.io/s/nj3u0q?file=/demo.tsx In the Demo, the AppBar, Drawer, and Main content are all contained within ...

The entry for package "ts-retry" could not be resolved due to possible errors in the main/module/exports specified in its package.json file

I encountered an error while attempting to run my React application using Vite. The issue arises from a package I am utilizing from a private npm registry (@ats/graphql), which has a dependency on the package ts-retry. Any assistance in resolving this pro ...

The functionality to generate personalized worldwide timezone pipe is not functioning

I'm completely new to Angular and I've been working on creating a custom pipe for adjusting timezones. The idea is to allow users to select their preferred timezone and have the offset applied accordingly. To start, I created a file called timez ...

What is a simple way to exclude a prop from a declaration in a React/TypeScript project in order to pass it as undefined

I'm trying to accomplish this task but encountering a typescript error indicating that the label is missing. Interestingly, when I explicitly set it as undefined, the error disappears, as shown in the image. Here's how my interface is structured ...

The absence of defined exports in TypeScript has presented a challenge, despite attempting various improvement methods

I've exhausted all available resources on the web regarding the TypeScript export issues, but none seem to resolve the problem. Watching a tutorial on YouTube, the presenter faced no such obstacles as I am encountering now. After updating the tsconf ...

Issue with ReactTS Route Triggering Invalid Hook Call

My implementation of the PrivateRoute component is as follows: interface Props { path: string, exact: boolean, component: React.FC<any>; } const PrivateRoute: React.FC<Props> = ({ component, path, exact }) => { return ( ...

Storing a byte array in a local file using JavaScript: A Step-by-Step Guide

Recently, I encountered an issue while working with an openssl certificate. Specifically, when I tried to download the certificate from my API, it returned byte arrays that I needed to convert to a PEM file in order to access them through another API. The ...

Utilizing NgClass Within an Attribute Directive in Angular 2.4.0

Is there a way to utilize NgClass within a custom attribute directive to modify the CSS class of the main elements? For example, if I have this code snippet: @Component({ selector: 'my-app', template: ` <div> <div class=" ...