Can I (somehow?) forbid the skipping of optional parameter in typescript?
class MyList {
constructor(
public head?: number,
public tail?: MyList
){}
}
const L0 = new MyList(); // <--- empty element list - good !
const L1 = new MyList(888); // <--- single element list - good !
const L2 = new MyList(777, L0); // <--- general list - good !
const L3 = new MyList(undefined, L1); // <--- forbid this
I want to statically enforce the following property on my list:
If head is undefined then tail is also undefined (and the list is empty)
Any TypeScript trick to achieve that? (This question is complementary to this question)
You can use something called overloading. This works for both methods and functions in TS. The basic idea is that you have single function/method implementation with all the possible arguments and you can specify different combintations of the function's arguments (just like for your case where you can have 0 arguments, only the first or both).
class MyList {
constructor()
constructor(head: number)
constructor(head: number, tail: MyList)
constructor(
public head?: number,
public tail?: MyList
){}
}
const L0 = new MyList(888);
const L1 = new MyList(777, L0);
const L2 = new MyList(undefined, L1); // This will show error: Argument of type 'undefined' is not assignable to parameter of type 'number'.
Related
Is there a way to create type in typescript with methods that when I call these methods, they have access to the variable's value? Equals what the array, for example, which has the method find.
Example:
const arrayVar: Array = [1,2,3];
array.find(el => el === 1);
In this case, find has access to the value of the array arrayVar without me having to pass it via a parameter to a function, for example, I wanted to create something in this way, for example:
const myVar: MyCustomType = 5;
myVar.add(2); // Nesse caso, o retorno seria 7.
I know it can be done with classes and functions, but then I would have to pass the value of "myVar" as a parameter (function add (value1, value2), for example), I wanted a way to access it directly, just like the type Array does in its methods.
To make a subclass of Number with new methods:
class SwagNumber extends Number {
add(number: number) {
// Tell the TS compiler that `this` is an unboxed Number
return (this as unknown as number) + number;
}
}
Then to use:
const six = new SwagNumber(6);
six will be typed to SwagNumber by the TS compiler.
And to show it works:
six.add(5)
> 11
Let's look at the Constructor used, part of the Class:
> six.constructor
[class SwagNumber extends Number]
This will also leave the original Number prototype unchanged, which will stop any potential issues (double dots are used to use a method on a number, to distinguish the dot from a decimal point!)
> 3..constructor
[Function: Number]
or:
> (3).constructor
[Function: Number]
See Classes on MDN
However there's some danger here
Since SwagNumber is an object, and a regular number isn't an object by default (until you call its methods), comparisons won't work properly:
> six === 6
false
See Why should you not use Number as a constructor?
You could do this by adding your add method to a prototype:
interface Number {
add: (val:number) => number;
}
Number.prototype.add = function(val:number): number{
return (this as number) + val;
}
var myVal: Number = 5
console.log(myVal.add(7))
TS Playground Link
If you want to invoke functions on an object at this point create a dedicated class. Why? Because you have structured zone where you can add/remove/edit and reuse in other part of code.
Sorry, i write in JS but you can change with no effort in TypeScript
Create Class Element
class CustomArray {
constructor() {
this.arr = [];
}
add(value) {
this.arr.push(value)
}
erase(){
this.arr = [];
}
// so on...
print(){
console.log(this.arr)
}
}
//I use Module, so i will use require.
module.exports = CustomArray;
The above class is simplest example. You can decorate with other functions. In theory as many as you want
FOR USE INSIDE OTHER CONTEXT
const CustomArray = require("./CustomArray");
var arr = new CustomArray();
arr.add(2)
arr.print()
This question already has answers here:
Get function parameter names and types in TypeScript
(1 answer)
How to get function parameter names/values dynamically?
(34 answers)
Closed 1 year ago.
I am trying to make a wrapper function which will take a function as an input and will return a newly typed function which allow both a list of parameters and an object which contain parameter names as keys.
I have written the following code for that. The code works as expected. The problem is that I have pass an additional type which contains the keys as parameter name and value as type of that parameter. I want to do this dynamically. I want to access the names of the parameters
//I want remove this Args parameters and make it dynamically using F type.
function wrapper<F extends (...args: any) => any, Args>(func: unknown) {
type ParametersList = Parameters<F>;
return func as (...args: [Args] | ParametersList) => ReturnType<F>;
}
const add = (x: number, y: number) => x + y;
const wrappedAdd = wrapper<typeof add, { x: number; y: number }>(add);
The Parameters function returns a named tuple(which is a new feature I guess). Is there any way we could get the names/labels of that tuple. You can also suggest any other way.
Thanks.
###Edit:
Ok I after some research I found out that we cannot get the names of the parameters of the function. So now my goal is to shorten my code a little bit. In the above code where I am passing an object in place of Args. I only want to pass an array of strings.
const wrappedAdd = wrapper<typeof add, ["x", "y"]>(add);
I want to generate an object dynamically using this array. Thanks
I think it is better to define any types in global scope.
type Elem = any;
type Predicate<Key extends number, Value extends Elem> = Record<Key, Value>
type Reduce<
Arr extends ReadonlyArray<Elem>,
Result extends Record<string, any> = {}
> = Arr extends []
? Result
: Arr extends [infer H]
? Result & Predicate<0, H>
: Arr extends readonly [...infer Tail, infer H]
? Tail extends ReadonlyArray<Elem>
? Reduce<Tail, Result & Predicate<Tail['length'], H>>
: never
: never;
function wrapper<F extends (...args: any) => any>(func: F):
(...args: [Reduce<Parameters<F>>] | Parameters<F>) => ReturnType<F> {
return func
}
const add = (x: number, y: string, z: number[]) => x + y;
const wrappedAdd = wrapper(add);
const add = (x: number, y: string, z: number[]) => x + y;
const wrappedAdd = wrapper(add)({ 0: 1, 1: 'hello', 2: [1] }); // ok
const wrappedAdd2 = wrapper(add)(1, 'hello', [1]); // ok
Btw, no need to use type assertion here. I mean you can get rid of as operator
If you are interested in more examples of such kind of types, you can take a look on my article
Here you have representation of Reduce type in almost pure JS:
const reduce = <T,>(arr: T[], cache = {}) => {
if (arr.length === 0) {
return cache
}
if (arr.length === 1) {
const [head] = arr;
return { ...cache, [0]: head }
}
const [head, ...rest] = arr;
return reduce(rest, { ...cache, [rest.length]: head })
}
Playground
Like #jcalz said:
Parameter names are not observable in the type system except as documentation for IntelliSense
There is no way to infer parameter name in TS and put it into another type, so I decided to use index type.
0 for first argument, 1 for second, etc ...
Regarding using argument names:
Docs
There is one place where the differences begin to become observable though: readability.
They’re purely there for documentation and tooling.
const add = (x: number) => x + x
type Args = Parameters<typeof add>
type Infer = Args extends [infer R] ? R : never // number
type Infer2 = Args extends [x: infer R] ? R : never // number
Hence, I don't think it is possible in current version of TypeScript.
I'm having trouble using a conditional type in combination with a computed object property name. Basically I'm inserting rows into a database based on an input string. Then I'm typing the return object based on that input string. One of the properties in the return object is a computed name that is also based on the input string. So it seems like typescript would have all the info needed to verify that this is correct but it keeps giving me errors. Here's a very simplified example.
//types of the table rows from the database
interface FirstTableRow {
id: number,
someFirstRefId: number
};
interface SecondTableRow {
id: number,
someSecondRefId: number
};
//maps which table we're working with to its reference column name
const whichToExtraColumn = {
first: 'someFirstRefId',
second: 'someSecondRefId'
} as const;
//maps the table to the returned row type
type ConstToObj<T> = (T extends 'first'
? FirstTableRow
: T extends 'second'
? SecondTableRow
: never
);
function createFirstOrSecond<
T extends keyof typeof whichToExtraColumn
>(
which: T
): ConstToObj<T> {
//gets the reference column name for this table
const refColumn = whichToExtraColumn[which];
//do database stuff....
const insertId = 1;
//build the inserted row
const test: ConstToObj<T> = {
id: insertId,
[refColumn]: 123
};
// ^ Type '{ [x: string]: number; id: number; }' is not assignable to type 'ConstToObj<T>'
return test;
};
I made a workaround by doing an if-check on refColumn, then generating different objects depending on that. But using a computed property name would be waayyy easier. Any help would be appreciated.
You are running into multiple issues here:
(1) Computed property names are widened, one might say this is a bug:
type Key = "a" | "b";
let a: Key = Math.random() ? "a" : "b";
const result = { [a]: 1 };
// -> { [x: string]: number }
So your example, [refColumn]: 123 will never behave as you want it to.
(2) Function bodies of functions with generic parameters are not validated iteratively with all possible subtypes (I guess the compiler might run forever then), instead they are validated with the type constraint. Thus if you have two generic types, whereas one is derived from the other, Typescript simply does not care. Usually this is not a problem because usually one type is directly the subtype of the other:
function assign<A extends B, B extends 1 | 2 | 3>(a: A) {
const b: B = a;
}
You've created a case where this is not the case, and constraint checking will always fail.
(3) One just cannot assign to a deferred conditional type. Typescript does not know which branch the conditional type will take (if it's evaluation is deferred), and as such only any can be assigned to it.
function plusOne<A extends 1 | 2>(a: A) {
const b: (A extends 1 ? 2 : 3) = a + 1;
}
So with these three limitations it is basically impossible to write your function without manual typecasts. This is one of the few cases were an as any seems very reasonable.
I am trying to call an async function and specify a custom type (let's call it "CustomType"). I've specified a few random properties in that type, it's just to understand that it's something that comes from a database (content could vary depending on the retrieved item, I have many different "CustomType" for each kind of file stored in a NoSql database).
This is the code testable by using https://flow.org/try
/* #flow */
// Type that would be returned in the case of usage of "mySpecificCallFunc(): Promise<Array<CustomType>>" defined below
declare type CustomType = {
prop1: boolean,
prop2: string,
prop3: Date
}
// Generic function that does the hard work
async function asyncFunc<T>(dateStart: Date, otherArgument: string):Promise<Array<T>>
{
let r:Array<T> = [] // do a call that returns an array that must be of a specific type
return r
}
// High level function that would send parameters to the generic function "asyncFunc"
async function mySpecificCallFunc(): Promise<Array<CustomType>>
{
let r = await asyncFunc<CustomType>(new Date(), 'test')
return []
}
Cannot reference type CustomType [1] from a value position.
Flow does not want the custom type to be used.
In C# this kind of generic usage would be totally acceptable so I do not understand why it is complaining?
It gives the following error :
20: let r = await asyncFunc<CustomType>(new Date(), 'test')
^ Cannot compare boolean [1] to string [2].
References:
20: let r = await asyncFunc<CustomType>(new Date(), 'test')
^ [1]
20: let r = await asyncFunc<CustomType>(new Date(), 'test')
^ [2]
20: let r = await asyncFunc<CustomType>(new Date(), 'test')
^ Cannot reference type `CustomType` [1] from a value position.
References:
3: declare type CustomType = {
^ [1]
UPDATE:
There is currently no link between "CustomType" and the request arguments.
In the real world scenario, it looks like this :
call: DbRetrieve('type1', param1, param2)
return: [{ _type: 'type1', prop1: true, prop2:'b' }] // an array containing <CustomType> objects
As you can see, there's no "shape" that can be defined from the arguments of the function asyncFunc because the arguments are not always linked to the properties of the returned object.
It's an ORM-like call, I just wanted to be able to specify the type without doing some "brute cast", but I may be following the wrong path because the type cannot be inferred from the usage...
You can not specifying type directly on call.
Your CustomType is an object, but in code you expect a boolean
So, first of all you need to make a link between an incoming and outgoing data:
async function asyncFunc<T>(p: T):Promise<T[]> {
return []
}
<T> in the function declaration is just like declaring a variable, but p: T and :Promise<T[]> make a dependency
Second, you need to make the T a little bit narrow by async function asyncFunc<T: CustomType>.... And change your type CustomType = boolean;
After that, you just need to call await asyncFunc(true); without any typing.
UPD:
You trying to specify a type the function should return, just on the function call - it`s not a right way not on flow and not on JS at all. A function result type should be certainly declared on the function declaration point - it may be a single type of several types combination (type0 | type).
Generic types is used to make a relations between parameters and result. So, you can, for example, make a function that get a param and returns an array of same types values like function doSmt<T>(a: T): T[] {return [a];}
I`m not sure what exactly you trying to do, but maybe you need something like this:
type CustomType<A, B> = {
prop0: A,
prop1: B,
}
function asyncFunc<C: string, D: number>(foo: C, bar: D): CustomType<C, D> {
// some actions
}
Suppose I'm writing code that handles UUIDs. Internally, I want to represent them as strings. That is, every UUID is a string, but not every string is a valid UUID, and I don't want to accidentally assign the wrong thing to a variable meant to hold a UUID. So I want to create a type 'uuid' such that this assignment would fail:
let foo: uuid = "Some string"
But this should succeed:
function create_uuid(): uuid; { /* implementation? */ }
let foo: uuid = create_uuid();
let bar: string = uuid; // this is fine
Is there any way to create a type with Flow that has these properties? I found $Subtype in my research, and thought this might work:
type uuid = $Subtype<string>;
But for some reason it still allows assignment from a string.
There is the following hack (the downside is that a UUID will be also an Object):
// keep this constructor private
class IsUUID {}
export type UUID = string & IsUUID;
export function create(): UUID {
const uuid = 'blah' // <= your implementation
return ((uuid: any): UUID)
}
// tests
declare function f(uuid: UUID): void;
declare function g(s: string): void;
declare function h(o: Object): void;
let foo = create()
let bar: string = foo // <= ok
f(foo) // <= ok
f(bar) // <= error: string. This type is incompatible with IsUUID
g(foo) // <= ok
g(bar) // <= ok
h(foo) // <= ok :(
Edit: This answer is out of date. Flow has implemented opaque types since this question was asked. Refer to ESRogs' answer.
There may be some hacks that can solve this problem, but what you are asking for is known as an opaque data type and Flow does not currently support them. Here are some discussions of them on the Flow repository on GitHub.
Use an opaque type with a subtyping constraint. From the docs:
exports.js
export opaque type ID: string = string;
imports.js
import type {ID} from './exports';
function formatID(x: ID): string {
return "ID: " + x; // Ok! IDs are strings.
}
function toID(x: string): ID {
return x; // Error: strings are not IDs.
}
https://flow.org/en/docs/types/opaque-types/