Conditional Type splits "function returning boolean" into "function returning true unions function returning false" - typescript-typings

In Typescript, Conditional Type splits "function returning boolean" () => boolean into "function returning true unions function returning false" () => true | () => false.
Could anyone explain why, and any solution to keep its returning type as "boolean"?
Example:
type A<U, E> = U extends E ? () => U : () => U;
const a: A<boolean, string> = () => Math.random() < 0.5;
Compile Error:
TS2322: Type '() => boolean' is not assignable to type '(() => false) | (() => true)'.
  Type '() => boolean' is not assignable to type '() => false'.
    Type 'boolean' is not assignable to type 'false'.

Related

How to get all hash values from a hashset in Redis using Typescript

Currently I'm using the same pattern that most examples online are using, but I keep running into this error
Argument of type '[string, (e: any, data: any) => any]' is not
assignable to parameter of type '[key: RedisCommandArgument] |
[options: CommandOptions, key:
RedisCommandArgument]'. Type '[string, (e: any, data: any) => any]' is
not assignable to type '[options:
CommandOptions, key: RedisCommandArgument]'.
This is the command I'm running:
const value = await this._redisClient.hGetAll(key, (err, val) => val);
In my code, the "key" argument is underlined by a red squiggly. I can't figure out why this is happening. The command works just fine if I don't add a callback.

Error ts(2322) trying to define a generic function signature in Typescript

I'm trying to define an interface like this:
interface ColumnGenerator {
columnName: string;
columnValuesAsArgs?: string[];
generator: <T = any[], R = any>(...args: T extends any[] ? T : [T]) => R;
}
the value of generator should be a function that takes any number of arguments (if any) of any type, and then return something.
But, when I do something like:
const generatedColumn: ColumnGenerator = {
columnName: 'creation_date',
generator: () => new Date()
}
the compiler yields the next error:
Type 'Date' is not assignable to type 'R'.
'R' could be instantiated with an arbitrary type which could be unrelated to 'Date'.
I'm fairly new to Typescript, so I don't have an idea of what am I doing wrong here. Could anyone give me an explanation?
the value of generator should be a function that takes any number of arguments (if any) of any type, and then return something.
The type that you described here is (...args: any[]) => any. That type is a function that takes any number of arguments of any type & then returns anything.
type ColumnGenerator = {
columnName: string;
columnValuesAsArgs?: string[];
generator: (...args: any[]) => any;
}
const generatedColumn: ColumnGenerator = {
columnName: 'creation_date',
generator: () => new Date()
}
I'm not entirely sure what you're trying to do, but if you want to constrain things a bit, you could constrain make the ColumnGenerator type generic & configure that when it's used:
type ColumnGenerator<T = any> = {
columnName: string;
columnValuesAsArgs?: string[];
generator: (...args: any[]) => T;
}
const generatedColumn: ColumnGenerator<Date> = {
columnName: 'creation_date',
generator: () => new Date(), // this will now error if `Date` isn't returned from this function
}
The problem is you are trying to carry type information in a type that doesn't support it. In other words, the Interface or type has a static definition, and making an instance of it doesn't change its type.
Think of it this way, If I take in a ColumnGenerator into my function, what is the return type of the generator? There is no way to tell other than to widen it all the way out, and have it be any. As mentioned, you also could make the ColumnGenerator itself generic.
An even simpler way is to just say that the generator itself (any) => any, but then use generics later to be more specific. The key is that the generatedColumn is not typed as a ColumnGenerator. It is in fact a more specific type. This allows you to act on the information on the generic later on. Typescript uses structural or "duck" typing. That is, if it quacks like a duck, it is a duck. So if you leave your variables as the exact type they are, you have more information later to use to infer what they acually look like.
For example:
type ColumnGenerator = {
columnName: string;
columnValuesAsArgs?: string[];
generator: (...args: any[]) => any;
}
const generatedColumn = {
columnName: 'creation_date',
generator: () => new Date()
}
function test<TCol extends ColumnGenerator>(col: TCol): ReturnType<TCol["generator"]> {
return col.generator();
}
//Date
let t = test(generatedColumn);

Accept which array method by caller in typescript

Background:
The only difference between the following functions is:
createGrid uses map (taking a callback returning type T) and returns T[][]
visitGridCoordinates uses forEach (taking a callback returning type void) and returns void.
const createGrid = <T>(width: number, height: number, callback: (x: number, y:number) => T) =>
Array
.from(Array(width).keys())
.map(
(x: number) => Array
.from(Array(height).keys())
.map((y: number) => callback(x, y))
);
const visitGridCoordinates = (width: number, height: number, callback: (x: number, y:number) => void) =>
Array
.from(Array(width).keys())
.forEach(
(x: number) => Array
.from(Array(height).keys())
.forEach((y: number) => callback(x, y);)
);
Question:
Is there a way to create a wrapper function over these that takes which method to use as an argument? I tried several things but kept running into problems.
const createGrid = verbGrid<string>(Array.prototype.map); // or ('map') alternately
const visitGridCoordinates = verbGrid(Array.prototype.forEach); // or ('forEach') alternately
const verbGrid = ???
I tried writing such a function, then used "infer parameter types from usages" and the result almost works:
const verbGrid = <U>(arrayMethod: (callbackfn: (value: any, index: number, array: any[]) => void, thisArg?: any) => void) => (
width: number,
height: number,
callback: ((x: number, y:number) => U) | ((x: number, y:number) => boolean)
) => {
const result: void | U[][] = arrayMethod.call(
Array.from(Array(width).keys()),
(x: number) => arrayMethod.call(
Array.from(Array(height).keys()),
(y: number) => callback(x, y)
)
)
return result;
};
But when I try to use createGrid its return type is always only void. And of course it is, because the type of arrayMethod says it returns void. When I try using unioned types for the entire type descriptor or just for the return value, various different errors occur.
I played around with using varof as in varof Array<number>["map"] | varof Array<number>["forEach"] to get more explicit with the types and avoid having to re-describe both method types. However I still had no luck (and I notice the map version doesn't specify U anywhere and I don't know how to do that).
Is this even possible, and if so, do you mind helping me understand where I'm going wrong?
Various results I've had:
The result of verbGrid being type unknown.
No errors in verbGrid but being unable to assign the result to a variable of type U[][] at the call site.
Passing in 'map' and 'forEach' as strings and using them as [method] (instead of doing method.call).
I tried to simplify things a little like so:
type ValueReturning = <T, U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any) => U[];
type VoidReturning = <T>(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any) => void;
class Enumerable {
static range(start: number, count: number) {
return Array.from(Array(count).keys()).map(index => start + index);
}
}
But this doesn't work at all ("Type 'ValueReturning' is not generic"):
const verbGrid = <U>(arrayMethod: ValueReturning<number, U> | VoidReturning<number>) => (
Appreciate any guidance.
Note: I am new to TypeScript but not new to Generics (from C#).
Addendum
In case it helps, here are the call sites to these two functions.
this._grid = createGrid(width, height, (x: number, y: number) => {
const cell = new Cell<string>(x, y);
this._cells.add(cell);
return cell;
});
visitGridCoordinates(width, height, (x, y) => {
getNeighborCoordinates(width, height, x, y)
.forEach(([neighborX, neighborY]) => {
this.grid[x][y].neighbors.add(this._grid[neighborX][neighborY]);
});
});
It's fine to suggest alternate methods for achieving the same goal, but I really was more interested in learning TypeScript better than I am in fixing this code to be perfectly optimal. (I spent a couple of hours creating a Boggle/Wordament solver last night—it works great and was fun to build.)
I ran into some annoying difficulties with this, like you did. One type function that I found useful is this:
type ArrayUnlessVoid<T> = T extends void ? void : Array<T>;
It's a conditional type that turns T into T[] unless T is void, in which case it's just void. We might want this because it unifies what map() and forEach() do: they each take a callback that returns a T and returns an ArrayUnlessVoid<T>.
Okay, here is the typing for verbGrid():
const verbGrid =
<R, O>(arrayMethod: (
callbackfn: (value: any, index: number, array: any[]) => R,
thisArg?: any
) => O) =>
(width: number, height: number, callback: ((x: number, y: number) => R)) => {
const outerArrayMethod = arrayMethod as any as (
callbackfn: (value: any, index: number, array: any[]) => O,
thisArg?: any
) => ArrayUnlessVoid<O>;
const result = outerArrayMethod.call(
Array.from(Array(width).keys()),
(x: number) => arrayMethod.call(
Array.from(Array(height).keys()),
(y: number) => callback(x, y)
));
return result;
}
Blecch. It would be nice if verbGrid could take something that turns a callback-returning-T into an ArrayUnlessVoid<T> and returns an ArrayUnlessVoid<ArrayUnlessVoid<T>>. But the compiler can't really see that Array.prototype.map() matches the former definition. So I add a new type parameter O to take the place of ArrayUnlessVoid<T> and return ArrayUnlessVoid<O> instead.
It would also be nice if there were some easier way to describe the two different contexts in which you use arrayMethod in the implementation. Instead I just give up and use a type assertion to give the outer call to arrayMethod a different type signature.
Anyway, you can see that these work as advertised:
const createGrid = verbGrid(Array.prototype.map);
// const createGrid: <U>(
// width: number, height: number, callback: (x: number, y: number) => U
// ) => U[][]
const visitGridCoordinates = verbGrid(Array.prototype.forEach);
// const visitGridCoordinates: (
// width: number, height: number,
// callback: (x: number, y: number) => void
// ) => void
Okay, hope that helps; good luck!
Playground link to code

Typescript: Evaluate type of generic function

Is there any trick to "evaluate" the type of a generic fuction?
Consider the following:
type Arr = <A>() => A[]
type Ev<G, A> = ???
Question: Is it possible to fill in ??? such that Ev<Arr, A> equals () => A[]? (As compared to <A>() => A[])
(Update 2022/04/26)
Something similar/related will be possible with TS 4.7; Even though that's not fully what we'd want here.
https://github.com/microsoft/TypeScript/pull/47607
Some more examples for the desired behavior:
Ev<<A>() => A[], number>
// should evaluate to
// () => number[]
Ev<<A>() => string, number>
// should evaluate to
// () => string
Ev<<A>() => [string, A], { some: "thing" }>
// should evaluate to
// () => [string, { some: "thing" }]
A simplified version of the question would be: Can we define
type EvNum<A> = ???
such that
EvNum<
<X>() => X
> // should be `number`
EvNum<
<X>() => X[]
> // should be `number[]`
EvNum<
<X>() => [X, "hi"]
> // should be `[number, "hi"]`
EvNum<
<X>() => SomeGenericType<X>
> // should be `SomeGenericType<number>`
EvNum<
<X>() => "constant"
> // should be `"constant"`
As to your first question, those are referred to as Higher Kinded Types, and are not supported in Typescript as of this answer.
A Higher Kinded Type is simply "A type that abstracts over some type that, in turn, abstracts over another type." So if you want a type, that you pass in a type to abstractly create a new type, that is an example of a higher kinded type. And it is not possible in TS.
You cannot pass a second type into a generic type and come out with a derived type.
Your last example (simplified) is literally ReturnType so not sure what you are meaning. It is perfectly possible to come up with. But you can't make a type that comes up with it.
type EvNum<T> = () => T;
type Arr<T> = T[];
function func<T>(param: EvNum<T>): T {
return param();
}
let x1 = func(()=> 4); //number
let x2 = func(()=> [4]); //number[]
let x3 = func(()=> [4, "hi"] as const); //[4, "hi"]
let x4 = func(()=> "constant" as const); //"constant"
let cool: Arr<number> = [4, 5, 6];
let x5 = func(() => cool); //Arr<number>
This passes your requested types
If I understand you correctly it should be:
type EV<T> = () => T;
Otherwise the question makes no sense or must be explained in more detail.

TypeScript: Returning `_.orderBy` from a function, what return type to use?

The packages I've got installed are lodash and #types/lodash.
I've got:
import _ from 'lodash';
function doSomething(): string[] {
const letters = ['c', 'a', 'b'];
return _.orderBy(letters, [null], ['asc']);
}
console.log(doSomething());
But return _.orderBy(letters, [null], ['asc']); throws the error:
Type '(string | number | (<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]) | (() => string) | (() => string) | (() => string | undefined) | ((...items: string[]) => number) | ... 25 more ... | { ...; })[]' is not assignable to type 'string[]'.
Type 'string | number | (<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]) | (() => string) | (() => string) | (() => string | undefined) | ((...items: string[]) => number) | ... 25 more ... | { ...; }' is not assignable to type 'string'.
Type 'number' is not assignable to type 'string'.
Yet clearly, an array of strings will be returned.
Is there any way to fix this, other than force-casting it to return _.orderBy(letters, [null], ['asc']) as string[];? Is there a way to specify the type for the orderBy be inherited from letters?
The problem you are having is because the value [null] you are passing to the iteratees argument (the second one) does not match the signature of the function.
The iteratees argument is documented to be of type (Array[]|Function[]|Object[]|string[]).
To fix your call, just replace the second argument with [_.identity], which is the default value, or alternatively set the second argument to undefined, which will also trigger the use of the default value. Check out the MDN Docs on Default Parameters.

Resources