Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a type in TypeScript for anything except functions?

Tags:

typescript

I would like to express that a paremeter should be an object or a simple value type (number, bool, string, etc.), but not a function.

If I use Object, the compiler let's me to assign a function.

var test: Object = () => "a";

If I use any, the same result of course too. Is there a type or trick which can help me out in this case?

My underlying goal is to garantee safety when using Knockout observables, so that I don't forget those little paranthesis to unwrap them :)

like image 262
Zoltán Tamási Avatar asked Jul 07 '14 15:07

Zoltán Tamási


People also ask

Is there an object type in TypeScript?

In TypeScript, object is the type of all non-primitive values (primitive values are undefined , null , booleans, numbers, bigints, strings). With this type, we can't access any properties of a value.

Can TypeScript types have functions?

In TypeScript, there are multiple syntaxes for declaring the type of a function: Method signatures. Function type literals. Object type literals with call/construct signatures.

Should you type everything in TypeScript?

Yes, you should make it a habit to explicitly set all types, it's will prevent having unexpected values and also good for readability.

What does type any mean in TypeScript?

The TypeScript any type allows you to store a value of any type. It instructs the compiler to skip type checking. Use the any type to store a value that you don't actually know its type at the compile-time or when you migrate a JavaScript project over to a TypeScript project.


2 Answers

New Feature Answer

Added November 2018 - as conditional types are a thing now!

Conditional types provide a possible solution to this, as you can create a NotFunction conditional type, as shown below:

type NotFunction<T> = T extends Function ? never : T;

This works as follows:

const aFunction = (input: string) => input;
const anObject = { data: 'some data' };
const aString = 'data';

// Error function is not a never
const x: NotFunction<typeof aFunction> = aFunction;

// OK
const y: NotFunction<typeof anObject> = anObject;
const z: NotFunction<typeof aString> = aString;

The only weakness in this is that you have to put the variable on the left side and right side of the statement - although there is safety if you make a mistake such as:

// Error - function is not a string
const x: NotFunction<typeof aString> = aFunction;

Original Answer

You can provide a runtime check using typeof, which although isn't a compile time check will catch those instances where you forget to execute the function:

function example(input: any) {
    if (typeof input === 'function') {
        alert('You passed a function!');
    }
}

function someFunction() {
    return 1;
}

// Okay
example({ name: 'Zoltán' });
example(1);
example('a string');
example(someFunction());

// Not okay
example(function () {});
example(someFunction);

Why can't you actually do what you want?

You almost can, because you could use an overload to allow "one of many types", for example:

class Example {
    someMethod(input: number);
    someMethod(input: string);
    someMethod(input: boolean);
    someMethod(input: any) {

    }
}

Here comes the rub: in order to allow object types, you would have to add an overload signature of someMethod(input: Object); or someMethod(input: {});. As soon as you do this, functions would become allowed, because function inherits from object.

If you could narrow down object to something less general, you could simply add more and more overloads (yikes) for all the types you want to allow.

like image 105
Fenton Avatar answered Nov 15 '22 05:11

Fenton


You can do this in TypeScript 2.8 with conditional types.

type NotFunc<T> = Exclude<T, Function>
function noFunc <T> (notF: T & NotFunc<T>) { return notF }

const f = () => 2

noFunc(f) // error!
noFunc(f()) // compiles!

If the type system can decide that T extends Function (i.e., T is a function) then the type will be never, which is a compile-time error. Otherwise, the type will be T and your code will compile.

However, you should read this to more clearly understand what is happening:

// type NotFunc<T> = T extends Function ? never : T
type NotFunc<T> = Exclude<T, Function>

// the imporant cases for us are any and {}
type AnyNotFunc = NotFunc<any> // any
type ObjNotFunc = NotFunc<{}> // {}
type NullNotFunc = NotFunc<null> // never

// some functions, explicitly typed and inferred
const f: Function = () => 2
const ff = () => 2
const g: Function = (): null => null
const gg = (): null => null

// so a function like this won't work:
function badNoFunc <T> (notF: NotFunc<T>) { return notF }

// these all compile, because T is inferred as {} and NotFunc<{}> is just {}
badNoFunc(f)
badNoFunc(g)
badNoFunc(ff)
badNoFunc(gg)

// so need the T & NotFunc<T> to give the compiler a hint as to the type of T
function noFunc <T> (notF: T & NotFunc<T>) { return notF }

// now f is correctly inferred to be Function
noFunc(f) // error! f is assignable to Function
noFunc(g) // error! g is assignable to Function
noFunc(f()) // OK! 2 is not assignable to Function

// but we would expect g() === null to be never since NotFunc<null> === never
noFunc(g()) // OK? even though null is assignable to Function?
noFunc<null>(g()) // Error! Ah, type Function represents () => any but NotFunc<null> is never

// if we use the implicitly typed version, gg, the compiler infers the null return value correctly
noFunc(gg()) // Error! Correct

noFunc(ff) // error! The type is correctly inferred to be function
noFunc(gg) // error! The type is correctly inferred to be function
noFunc(ff()) // OK! 2 is not assignable to Function

Takeaways:

  1. The OP can do what they want
  2. The Function type is as bad as any, so avoid it
like image 28
Ziggy Avatar answered Nov 15 '22 04:11

Ziggy