0

This will take a bit to explain.

I have a class called Dependency, which holds a value of generic type T and provides some helper methods (not really relevant here):

class Dependency<T> {
    value: T
    ...
}

I'm trying to write another class, Derivative, that takes a list of dependencies and a derive function to generate some other value:

type DependencyArray = Array<Dependency<any>>
type DeriveFunc<T, D extends DependencyArray = DependencyArray> = (...deps: D) => Promise<T>

class Derivative<T, D extends DependencyArray = DependencyArray> {
    value: T
    constructor(
        dependencies: D,
        derive: DeriveFunc<T, D>,
    ) { ... }
}

Here's the catch – I want to restrict derive to only accept dependencies provided by first argument to Derivative constructor. For example:

const depA = new Dependency<string>()
const depB = new Dependency<boolean>()

// Good: this one compiles
const new Derivative<string>([depA, depB], async ([a, b]) => `${a}-${b}`)

// Good: these two fail to compile, since arrays aren't the same length
// TS2345: Source has 2 element(s) but target allows only 1.
const new Derivative<string>([depA, depB], async ([a]) => `${a}`)
const new Derivative<string>([depA], async ([a, b]) => `${a}`)

// Bad: in this one, `derive` function doesn't understand which types are in dependency array, so it merges it to Dependency<string | boolean>[]
// TS2339: Property 'length' does not exist on type 'boolean | string'.
//     Property 'length' does not exist on type 'false'.
const new Derivative<string>([depA, depB], async ([a, b]) => `${a.length}-${b}`)

Is there a way to reference exactly the dependencies that were passed to Derivative constructor?

2 Answers 2

1

Currently, TypeScript doesn't support what you're trying to do, since the types of array literals ([1, 5, true, null]) aren't imlied to tuples ([ number, number, boolean, null]), but instead to an array type ((number | boolean | null)[]). Unfortunately, there's nothing you can do about it. There's an issue you can take a look at, it describes exactly what you're trying to do.

Regardless, you can use the spread syntax instead:

class Derivative<T, D extends DependencyArray = DependencyArray> {
    value: T
    constructor(
        derive: DeriveFunc<T, D>,
        ...dependencies: D
    ) { ... }
}

In this way, argument types will get implied correctly. Unfortunately, there are no other workarounds, and if you want to use an array, you don't have a particular choice.

Sign up to request clarification or add additional context in comments.

Comments

0

It turns out using objects instead of arrays works just fine. I had to switch to something like that:

type DependencyMap = { [key: string]: Dependency<any> }
type DeriveFunc<T, D> = (deps: D) => Promise<T>

const new Derivative<string>({ depA, depB }, async ({ depA, depB }) => `${depA.length}-${depB}`)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.