What is the difference between Arrow Functions and Regular Functions in implementing Interfaces, so that code A causes compile-time error and code B compiles successfully.
Note: in tsconfig.json
all strict type-checking options are enabled, including strictFunctionTypes
, BTW it supposed that by enabling strict
all strict type-checking options get enabled.
Code A that causes compile time error
interface SomeInterface {
someFunction: (par1: string | undefined) => string;
}
class SomeClass implements SomeInterface {
someFunction(par1: string): string //invalid function signature
{
throw new Error('Method not implemented.');
}
}
And, Code B that compiles successfully.
interface SomeInterface {
someFunction(par1: string | undefined): string;
}
class SomeClass implements SomeInterface {
someFunction(par1: string): string //invalid function signature
{
throw new Error("Method not implemented.");
}
}
Playground Link
An arrow function expression is an anonymous function expression written with the “fat arrow” syntax ( => ). Like traditional function expressions, arrow functions are not hoisted, and so you cannot call them before you declare them. They are also always anonymous—there is no way to name an arrow function.
It's a new feature that introduced in ES6 and is called arrow function. The left part denotes the input of a function and the right part the output of that function.
Arrow functions cannot be used as constructors. They cannot be called with the new keyword. Doing that throws an error indicating that the function is not a constructor. As a result, bindings such as new.
An Arrow function should not be used as methods. An arrow function can not be used as constructors. An arrow function can not use yield within its body. Arrow function cannot be suitable for call apply and bind methods.
Most programmers are used to the traditional way of writing functions, and arrow functions change this completely. This makes code harder to read and might take a while for someone newer to grasp the code. Therefore in such circumstances, developers may choose to use regular functions rather than arrow functions.
Default "this" context Arrow functions do not bind their own this, instead, they inherit the one from the parent scope, which is called "lexical scoping". This makes arrow functions to be a great choice in some scenarios but a very bad one in others If we look at the first example but using arrow functions
Everything before the arrow is arguments of the function and everything after the arrow is always returned as the result of the function. If you need a function that contains multiple statements you can still do this:
It is throwing error — the prototype of an arrow function is undefined . Arrow functions can never be called with the new keyword because they do not have the [ [Construct]] method. Therefore, the prototype property also does not exist for arrow functions.
With --strictFunctionTypes
enabled, function types' parameters are checked contravariantly, as required to maintain type safety:
class SomeClass implements SomeInterface { // error here
someFunction(par1: string): string
{
return par1.toUpperCase();
}
}
const i: SomeInterface = new SomeClass(); // error here
i.someFunction(undefined); // runtime error here, par1 is undefined
But, as mentioned in the documentation:
During development of this feature, we discovered a large number of inherently unsafe class hierarchies, including some in the DOM. Because of this, the setting only applies to functions written in function syntax, not to those in method syntax.
So method types' parameters are still checked bivariantly, meaning both contavariantly and covariantly, in order to support some common patterns (although perhaps generics or polymorphic this
would be a better way to do it for some of these cases). The big example is Array<T>
, where people apparently like their covariant arrays:
interface Animal { species: string }
interface Dog extends Animal { bark(): void };
const scooby: Dog = { species: "dog", bark() { console.log("ROOBY ROO or whatevz") } };
const snoopy: Dog = { species: "dog", bark() { console.log("...") } };
function processAnimals(arr: Animal[]) {
console.log("I got " + arr.map(x => x.species).join(", ") + ".")
};
const dogs = [scooby, snoopy];
processAnimals(dogs); // okay
This is ergonomic and common, but technically the compiler should reject dogs
because Dog[]
would not be a valid Animal[]
(indeed, methods like push()
will do bad things like push a Cat
into a Dog[]
under the hood). But if you go down this route you find out that TypeScript is unsound all over the place in exactly this way, even without function parameters, because property writes are also like this. See this Q/A for more elaboration.
And that means your SomeClass2
produces no error since SomeInterface2
uses method syntax:
class SomeClass2 implements SomeInterface2 { // no error
someFunction(par1: string): string {
return par1.toUpperCase();
}
}
Of course this has exactly the same soundness problem as before:
const i2: SomeInterface2 = new SomeClass2(); // no error
i2.someFunction(undefined); // runtime error, par1 is undefined
But that's just the way it is. Methods are less safe than functions by design, for convenience.
Playground link to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With