diff --git a/Programming_Languages/TypeScript/Further_examples_of_generics.md b/Programming_Languages/TypeScript/Further_examples_of_generics.md index b175411..ea4d365 100644 --- a/Programming_Languages/TypeScript/Further_examples_of_generics.md +++ b/Programming_Languages/TypeScript/Further_examples_of_generics.md @@ -4,3 +4,117 @@ categories: tags: - typescript --- + +# Further examples of generics in TypeScript + +## Basic function +In the code below we have a simple JavaScript function that receives a value and an an array as parameters. It returns a new array comprising the original array plus the additional value: + +```js +function generateArray(existingArray, newValue) { + return [...existingArray, newValue]; +} +``` + +Imagine that we want to ensure that each of the parameters share the same data type. In other words: if the function is passed a string array, the second parameter must also be a string. For example, it should not be the case that you can append a string to an array of numbers. + +Now imagine that we don't know in advance what type the value or array will be, we just know that the data types of the parameters must match. + +In converting the function to TypeScript, one way of overcoming our lack of foreknowledge would be to deploy `any`. This way it doesn't matter which types are passed to the function: + +```ts +function generateArray(existingArray: any[], newValue: any): any[] { + return [...existingArray, newValue]; +} +``` + +But this is no solution at all. The problem — as always with `any` — is that it strips our function of any type checks whatsoever and would therefore invite calls of form: `generateArray([1,2,3], 'lorem')`. + +Enter generics: + +```ts +function generateArray(existingArr: T[], newValue: T): T[] { + return [...existingArr, newValue]; +} +``` + +Now, whilst we haven't asserted ahead of time which types will be used, whichever types we do pass in, must match. The function header is saying: + +- both arguments must be of the same type (represented by `T`) +- the function will return an array of this same `T` type. + +If I then tried to run the function with unmatched types (for example `generateArray([1,2,3,4], true)` ) TypeScript would raise the following error: + +``` +Argument of type 'boolean' is not assignable to parameter of type 'number' +``` + +Note that even though the function in question does not express any preference for number types, given that our first parameter is a number, TypeScript knows that the second parameter must also be a number. + +> In the generic function we have used `T` as our placeholder for a generic type as this is the convention. However there is no compunction to do so. We could have used any letter or string, providing that the string is not a reserved term. + +### More advanced function + +This example demonstrates how we can use generics to reduce repetition when writing functions and is also a more realistic use case. + +Let's say we have two types or interfaces: + +```tsx +type VideoFormatUrls = { + format720p: URL; + format1080p: URL; +}; +``` + +```tsx +type SubtitleFormatUrls = { + english: URL; + german: URL; +}; +``` + +An example of an object matching these type definitions: + +```tsx +const videoFormats: VideoFormatUrls = { + format720p: https://www.format720p.co.uk, + format1080p: https://www.format1080p.co.uk +} +``` + +Imagine we wanted to be able to check whether a given film is available in a certain video format. We could write a function like this: + +```tsx +function isFormatAvailable(obj: VideoFormatUrls, format: string): format is keyof VideoFormatUrls { + return format in obj; +} +``` + +Now imagine that we need to do the same thing with subtitles, but given that `isFormatAvailable()` is typed to the `VideoFormatUrls` type we would get an error if we used this function for subtitles. But we also don't want to write a near identical function typed to `SubtitleFormatUrls` to subtitles just to ensure adequate type safety. + +Alternatively we could use a union type, for example: + +```tsx +function isFormatAvailable( + obj: VideoFormatUrls | SubtitleFormatUrls, + format: string, +): format is keyof VideoFormatUrls { + return format in obj; +} +``` + +But this quickly becomes unwieldy if we, for the sake of argument have a great many URL types that we want the function to utilise. + +This is where generics become super helpful. Here is how we would rewrite the function as a generic: + +```tsx +function isAvailable(obj: Formats, key: string): key is keyof Formats { + return key in obj; +} +``` + +We could then explicitly type our calls of this function, viz: + +```tsx +isFormatAvailable(subtitles, 'english'); +``` diff --git a/_meta/Resources.md b/_meta/Resources.md index d555e9d..e353ebd 100644 --- a/_meta/Resources.md +++ b/_meta/Resources.md @@ -104,6 +104,8 @@ J. Hunt. 2019. **An Advanced Guide to Python Programming** [Learning Arduino with Python](https://realpython.com/arduino-python/) +[Python Programming Projects](https://github.com/zhiwehu/Python-programming-exercises/blob/master/100%2B%20Python%20challenging%20programming%20exercises.txt) + ## Shell B. Perry, D. Taylor. 2015. **Wicked Cool Shell Scripts**