Created
January 11, 2022 16:03
-
-
Save danielgek/085a1ecbc39f880d780fde50f7c1cfdb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let hello = "a string"; | |
hello = '5'; | |
const shouldBeAString: boolean = true; | |
function addOne(x: number) { | |
return x + 1; | |
} | |
const four = addOne(3); | |
const notANumber = addOne(10); | |
function timesThree(x: number) { | |
return x * 3; | |
} | |
timesThree(12); | |
const timesFour = (x: number) => x * 4; | |
/* | |
* ====================================================== | |
* TODO: Update Earthlings to allow these values: | |
* ======================================================*/ | |
type AnimalTypes = "animal" | "vegetable" | "mineral"; | |
type Earthling = { | |
type: AnimalTypes; | |
name?: string; | |
}; | |
let dog: Earthling = { type: "animal", name: "Fido" }; | |
let cat: Earthling = { type: "animal", name: "Suki" }; | |
let zucchini: Earthling = { | |
type: "vegetable", | |
name: "Zucchini" | |
}; | |
let rose: Earthling = { type: "vegetable", name: "Rose" }; | |
let quartz: Earthling = { type: "mineral", name: "Quartz" }; | |
let diamond: Earthling = { type: "mineral", name: "Diamond" }; | |
/* typings:expect-error */ | |
const invalidEarthling: Earthling = { type: "martian", name: "Quux" } | |
/* typings:expect-error */ | |
const invalidEarthling2: Earthling = { type: "plutonian" } | |
/* | |
* ====================================================== | |
* TODO: Update Aliens to allow these values: | |
* ======================================================*/ | |
type Alien = { | |
name: string; | |
homePlanet: string; | |
phaser: boolean; | |
}; | |
let alienBlaxnor: Alien = { | |
name: "Blaxnor", | |
homePlanet: "Jupiter", | |
phaser: true | |
}; | |
let alienXanter: Alien = { | |
name: "Xanter", | |
homePlanet: "Mars", | |
phaser: false | |
}; | |
/* | |
* ====================================================== | |
* TODO: Confirm that EarthlingOrAlien allows and disallows | |
* the following values | |
* ======================================================*/ | |
type EarthlingOrAlien = Earthling | Alien; | |
let person: EarthlingOrAlien = { type: "animal", name: "Fernando" } | |
let alien: EarthlingOrAlien = { name: "Mac", homePlanet: "Pluto", phaser: true } | |
// typings:expect-error | |
let star: EarthlingOrAlien = {name: "Sirius", homePlanet: "jk rolling", phaser: false}; | |
// typings:expect-error | |
let galaxy: EarthlingOrAlien = { | |
phaser: false, | |
homePlanet: "LocalGalaxy", | |
name: "Milky Way" | |
}; | |
// typings:expect-error | |
let asteroid: EarthlingOrAlien = { | |
phaser: false, | |
homePlanet: "false", | |
name: "Asteroid" | |
}; | |
/** | |
* If we have a value of a union type, we can access common properties | |
*/ | |
function printName(creature: EarthlingOrAlien){ | |
console.log(creature.name) | |
} | |
/** | |
* But we cannot access properties that aren't common to all constituents | |
*/ | |
function printType(creature: EarthlingOrAlien) { | |
// typings:expect-error | |
console.log(creature.type); | |
} | |
/* | |
* Intersections allow us to combine type definitions to | |
* create a single type with all the attributes of both types. | |
* This is useful when we have some set of properties that are | |
* shared among many different types. */ | |
type Cat = { | |
animalType: "cat"; | |
breedName: string; | |
coloration: "tabby" | "solid-colored" | "spotted"; | |
}; | |
type Dog = { | |
animalType: "dog"; | |
breedName: string; | |
size: "teacup" | "toy" | "standard" | "huge"; | |
}; | |
type PetInfo = { name: string; familyName: string }; | |
// We can define types that are both pet and Cat (or Dog) by intersecting | |
// the PetInfo type with the species type. | |
type PetCat = PetInfo & Cat; | |
type PetDog = PetInfo & Dog; | |
/* | |
* ====================================================== | |
* TODO: Describe a valid PetCat and PetDog below. | |
* HINT: Use autocompletion (ctrl-space) to help you fill in the properties. | |
* ======================================================*/ | |
const sukiTheCat: PetCat = { | |
name: "Nuts", | |
animalType: "cat", | |
breedName: "bread cat", | |
coloration: "tabby", | |
familyName: "bread cat" | |
}; | |
const finnTheDog: PetDog = { | |
name: "Amendoin", | |
animalType: "dog", | |
breedName: "pastor", | |
familyName: "serra", | |
size: "toy" | |
}; | |
function announcePet(pet: PetInfo) { | |
return `This is ${pet.familyName} family pet, ${pet.name}.`; | |
} | |
/* | |
* Notice that announcePet will take any object that's | |
* structurally compatible with type Pet. */ | |
announcePet(sukiTheCat); | |
announcePet(finnTheDog); | |
/* ====================================================== | |
* TS understands control flow. | |
* | |
* TODO: Take a look at the type of 'fruit' each time it's referenced in | |
* isBanana below. | |
* ====================================================== */ | |
function isBanana(fruit: 'banana' | 'apple' | 'pear') { | |
if (fruit === "banana") { | |
return `${fruit} is a banana`; | |
} else { | |
return ` ${fruit} is not a banana`; | |
} | |
} | |
/* ====================================================== | |
* TODO: Check out the return type of classify. TS has figured out | |
* which specific strings can possibly be returned. | |
* ====================================================== */ | |
function classify(n: number) { | |
if (n < 0) return "negative"; | |
if (n > 0) return "positive"; | |
return "zero"; | |
} | |
/* | |
* TS checks our declared return type against the inferred type | |
* from all code paths in a function. | |
* | |
* This makes it easy to tell if we've handled all cases. | |
* | |
* ====================================================== | |
* TODO: Try commenting out one of the return statements in | |
* describeNumber and see what happens. | |
* ======================================================*/ | |
function describeNumber(num: number): string { | |
const value = classify(num); | |
if (value === "negative") { | |
return `${num} is a negative number`; | |
} else if (value === "positive") { | |
return `${num} is a positive number`; | |
} else { | |
return `${num} is zero`; | |
} | |
} | |
/* | |
* ====================================================== | |
* TODO: Change ONLY the input type FruitColor so that the | |
* function below will only accept inputs it is able to classify | |
* ======================================================*/ | |
type FruitColor = "red" | "yellow"; | |
function appleOrBanana(fruitColor: FruitColor): "apple" | "banana" { | |
switch (fruitColor) { | |
case "red": | |
return "apple"; | |
case "yellow": | |
return "banana"; | |
} | |
} | |
/* | |
* Let's take a look at the HotDrink type described in the slides. | |
* | |
* TS can narrow types as we move through control flow. When | |
* we're handling different cases of a union type, it's helpful | |
* to have a single property that is shared between all the cases, | |
* but has a different literal value for each case in the union. This allows | |
* us to use type narrowing to determine which case we're dealing with. | |
* | |
* This is great for when we have a bunch of similar objects with | |
* different constraints | |
* | |
* This technique - uninioning object types together with a shared, discriminating field - | |
* is called a "discriminated union" | |
*/ | |
type Tea = { | |
type: "tea"; // Discriminant field | |
style: "green" | "black" | "herbal"; | |
name: string; | |
}; | |
type Coffee = { | |
type: "coffee"; // Discriminant field | |
roast: "dark" | "medium" | "light"; | |
name: string; | |
}; | |
type HotDrink = Tea | Coffee; | |
/** | |
* Since type is common to both Tea and Coffee, we can test it to | |
* figure out which we're dealing with. | |
* | |
* ====================================================== | |
* TODO: Write a function that takes a HotDrink and | |
* returns the style, for tea, or the roast, for coffee. | |
* ====================================================== | |
*/ | |
function describe( | |
drink: HotDrink | |
): "green" | "black" | "herbal" | "dark" | "medium" | "light" { | |
return drink.type === "tea" ? drink.style : drink.roast | |
} | |
const rachaelsDrink: Tea = {name: "Chamomile", style: "herbal", type: "tea"} | |
const drewsDrink: Coffee = {name: "Onyx Columbia San Jose", roast: "light", type: "coffee"} | |
const herbalStyle = describe(rachaelsDrink); | |
const lightStyle = describe(drewsDrink); | |
/* | |
* ====================================================== | |
* TODO: Define FruitType as a discriminated union so that | |
* the type proves that apples are red and can be polished, | |
* and bananas are yellow and can be peeled. | |
* ====================================================== | |
*/ | |
type FruitFunction = { | |
eat: () => void | |
} | |
type FruitType = { | |
type: "apple"; | |
color: "red" | |
polish: () => void | |
} & FruitFunction | { | |
type: "banana"; | |
color: "yellow"; | |
peel: () => void | |
} & FruitFunction; | |
// Hint: To define a function property within an object, write something like: | |
// type SomeObject = { | |
// myFunc: () => void | |
// }; | |
function doSomething(fruit: FruitType) { | |
// FruitType must have a `type` field as a discriminant | |
switch (fruit.type) { | |
case "apple": | |
// Apples must be the color red in this example | |
const color: "red" = fruit.color | |
console.log(color); | |
// For this example, apples MUST NOT be yellow | |
// typings:expect-error | |
const wrongColor: "yellow" = fruit.color | |
console.log(wrongColor); | |
// Apples must have a polish function property | |
fruit.polish(); | |
fruit.eat(); | |
// Apples must not have a peel function property | |
// typings:expect-error | |
fruit.peel(); | |
break; | |
case "banana": | |
// Bananas must be yellow in our example | |
const color: "yellow" = fruit.color | |
console.log(color); | |
// Bananas must not be red. | |
// typings:expect-error | |
const wrongColor: "red" = fruit.color | |
console.log(wrongColor); | |
fruit.eat(); | |
// We can peel a banana | |
fruit.peel(); | |
// But polishing a banana would be silly. | |
// typings:expect-error | |
fruit.polish(); | |
break; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment