Typescript: Evaluate type of generic function - typescript-typings

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.

Related

Typescript, Enums with strings and numbers

I have an interface with
interface mathTest {
mathAction: MathActionEnum;
}
The reason for this is that I want this property to have just one of the specific values from the enum below.
enum MathActionEnum {
'byOne' = 1,
'byTwo' = 2,
'byFour' = 4,
'byEight' = 8,
}
Assume mathAction = 'byOne' -> received from an API response.
First scenario: doing an arithmetic operation, I need the number value: let result: number = amount / MathActionEnum[mathAction] but I get an error:
The right-hand side of an arithmetic operation must be of type 'any',
'number', 'bigint' or an enum type
It is a number but still I need to cast it with Number(MathActionEnum[mathAction]) for the error to go away.
Second scenario: equality check, I need the string value: if (mathAction === MathActionEnum[MathActionEnum.byOne]) but I get an error:
This condition will always return 'false' since the types
'MathActionEnum' and 'string' have no overlap
Which makes sense.
I'm a bit lost, is there a way to syntax it as I expect it to be? Maybe I need to define things differently?
Thanks
TypeScript enums are absolutely NOT suitable for any sort of key-value mapping. The intent is to have a grouping of uniquely identifiable labels, but labels are where it ends. While they may indeed have a number representation under the hood, they are not intended for use as a key-value store. You will have to cast it to "extract the number", and then the type is just number, so you effectively defeat the purpose of enums.
For all intents and purposes, think of them like keys with no useful values:
const MathActionEnum = Object.freeze({
byOne: Symbol(),
byTwo: Symbol(),
byFour: Symbol(),
byEight: Symbol(),
})
Consider the newer alternative, const assertion, instead. They'll provide you with type safety on both keys and values:
const MathActions = {
'byOne': 1,
'byTwo': 2,
'byFour': 4,
'byEight': 8,
} as const
type MathAction = keyof typeof MathActions
type MathActionValue = typeof MathActions[MathAction]
You get full type safety on both keys and values:
const example = (action: MathAction) => {
return 2 * MathActions[action]
}
example('byOne')
// compile error, not a valid key
example('foo')
// -------------
const example2 = (actionValue: MathActionValue) => {
return 2 * actionValue
}
example2(4)
// compile error, not a valid value
example2(19)
You can even add type assertions to check if arbitrary values are a key or value:
const isAction = (action: string): action is MathAction => {
return Object.keys(MathActions).includes(action)
}
isAction
const isActionValue = (actionValue: number): actionValue is MathActionValue => {
return Object.values(MathActions).includes(actionValue as any)
}
You'll even get IDE autocompletion for both keys and values:
Here's a Playground

How to define a macro vecvec to initialize a vector of vectors?

Just like vec![2,3,4], can we define a similar macro vecvec to initialize vector of vector. Eg.
let vv0 = vecvec![[2,3,4],[5,6,7]]; // vec of 2 vecs
let vv1 = vecvec![[1,2,3]];
let vv2 = vecvec![[1,2,3], []];
let vv3 = vecvec![[1,3,2]; 2];
You just need to think through the problem. You really only have 2 main cases. The first case being if elements are listed (Ex: a, b, c) and the second where a single value and length are given (Ex: a; b). We can even check our work by reading the documentation for vec!. In the documentation we can see vec! is defined as follows:
macro_rules! vec {
() => { ... };
($elem:expr; $n:expr) => { ... };
($($x:expr),+ $(,)?) => { ... };
}
As you can see, they have 3 cases. We didn't specify the the case were no items are included, but that does not really matter since your macro can call vec! and have it handle that case for you.
We can just copy the cases in their macro and add the functionality inside. The only other issue that might stop you is that [a, b, c] is an expression in of itself. Luckily we can just skip that by specifying items as requiring brackets and pick out the items ourselves before passing them off to vec!.
macro_rules! vecvec {
([$($elem:expr),*]; $n:expr) => {{
let mut vec = Vec::new();
vec.resize_with($n, || vec![$($elem),*]);
vec
}};
($([$($x:expr),*]),* $(,)?) => {
vec![$(vec![$($x),*]),*]
};
}
Instead of defining a new macro. You can initialize the vector of the vector.
In the example below, I'm explicitly setting type. It's not necessary but a good practice.
let vv0:Vec<Vec<u32>> = vec![vec![2,3,4],vec![5,6,7]];
let vv1:Vec<Vec<u32>> = vec![vec![2,3,4],vec![5]];
let vv2:Vec<Vec<u32>> = vec![vec![],vec![5,6,7]];
let vv3:Vec<Vec<u32>> = vec![vec![2,3,4],vec![]];

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 - Nested arrow function typing

I have this code for deferring the execution of a function
export type DeferredFunction<T> = () => T | PromiseLike<T>;
export class Deferrable<T> {
protected df: DeferredFunction<T>;
constructor(df: DeferredFunction<T>) {
this.df = df;
}
public async execute(): Promise<T> {
return this.df();
}
}
export const defer = <T>(df: DeferredFunction<T>): Deferrable<T> => new Deferrable<T>(df);
That works fine and I can run code like
await defer(() => someFunction('foo', 'bar')).execute();
but I what I want to do is type DeferredFunction in a way that I can specify the inner function's signature but I can't get it working. In generic cases the above works but when I want to limit the arguments such that they are specific to a certain type of function I don't have that kind of control.
For clarity, I want to be able to type the inner function's inputs like (as an example)
export type InnerDeferredFunction<T> = (a: string, b: number, c: SomeObjectType) => T | PromiseLike<T>
Any help would be greatly appreciated!
What "inner function" are you talking about? Is it someFunction? If so then the type of DeferredFunction<T> has no handle on it, since it's a function called by the implementation of DeferredFunction<T>. There's no way in TypeScript to specify "a function whose implementation must call a function of type (x: string, y: number, z: boolean) => string". Implementation details are not part of a function's call signature.
The only way I can imagine to begin to approach this would be for DeferredFunction<T> to accept as a parameter the inner function you want to call, along with the list of arguments to call it with. This might not be what you're looking for, but it's the closest that the type system can represent.
Something like this:
export type InnerDeferredFunction<T, A extends any[]> = (...args: A) => T | PromiseLike<T>;
export type ZeroArgDeferredFunction<T> = InnerDeferredFunction<T, []>
Here I'm keeping A generic but you can specify it to some hardcoded list of arguments. I've renamed your DeferredFunction to ZeroArgDeferredFunction to be explicit that it doesn't need arguments.
But now Deferrable needs to know about T and A:
export class Deferrable<T, A extends any[]> {
protected df: ZeroArgDeferredFunction<T>;
constructor(df: InnerDeferredFunction<T, A>, ...args: A) {
this.df = () => df(...args);
}
public async execute(): Promise<T> {
return this.df();
}
}
And you can see that you have to construct one by passing it the inner function and its arguments, and the ZeroArgDeferredFunction is built inside the constructor and is not passed in.
There are different ways to define defer(). It could be a thin wrapper around new Deferrable the way you had it, or you could imagine splitting it up so that the args come first:
export const defer = <A extends any[]>(...args: A) => <T>(
df: InnerDeferredFunction<T, A>): Deferrable<T, A> => new Deferrable<T, A>(df, ...args);
And then you can test it like this:
function someFunction(x: string, y: string) {
return (x + y).length;
}
function anotherFunction(x: number, y: number) {
return (x * y).toFixed()
}
const deferFooBar = defer('foo', 'bar');
await deferFooBar(someFunction).execute(); // okay
await deferFooBar(anotherFunction); // error! string is not assignable to number
Once you call deferFooBar('foo', 'bar'), the returned value will only accept functions that can be safely called with the arguments foo and 'bar'. That means someFunction will be accepted and anotherFunction will be rejected.
Okay, hope that helps; good luck!
Playground link to code

Define the type of an object with string keys and function values

I have a node JS function with one parameter that is an object with keys and the values are functions that then resolve to the underlying values.
We have just switched over to TS and I don't know how to define the key:value types of a parameter and further I don't know how to define a function as the value type?
The TS function looks like this...
const myJSFunction = o => input => ...
Where o is the string:function object. And input is then passed into each of the function values of o.
So I was thinking of having some signature along the lines of...
// define the generic <R> function as <U => any>
// define the generic <T> as an object of { string : R }
const myTSFunction = (o: T) => (input: U) => ...
Or something? I'm clutching at straws here as I don't know Typescript well enough to know what is possible with generics.
Thanks
What about something like this :
// We define what the type o is
// key: string means "any key should ..."
interface Obj<T> {
[key: string]: (input: T) => void,
};
// We instantiate an object for the test
const o: Obj<string> = {
a: (input) => { },
b: (input) => { },
};
// We define the function to work with any type of value of obj
// and call it for the test
function myTSFunction<T>(obj: Obj<T>, val: T): void {
obj[0](val);
}
Grégory NEUT answer helped a lot but there were some other constraints that I discovered on the way (that JS had been hiding away). I'm using Lodash so my object was not just an object but a type that they had defined.
So I defined some new types...
type TransformerFunction<T> = (o: T) => any;
type TransformerObject<T> = Dictionary<TransformerFunction<T>>;
And then the function became like...
export const objectTransform = <T extends any>(o: TransformerObject<T>) => <U extends T>(json: U): Dictionary<any> => _.flow(
_.mapValues((f: TransformerFunction<T>) => f(json)),
_.omitBy(_.isUndefined),
_.omit('undefined'),
)(o);
This is how I have been transforming JSON in JS and now moving it over to TS and loving the generics.

Resources