Diese Präsentation wurde erfolgreich gemeldet.
Die SlideShare-Präsentation wird heruntergeladen. ×

Typowanie nominalne w TypeScript

Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Wird geladen in …3
×

Hier ansehen

1 von 224 Anzeige

Typowanie nominalne w TypeScript

Herunterladen, um offline zu lesen

TypeScript na przestrzeni lat udowodnił że da się okiełznać dużą część problemów wynikających z dynamiczności języka JavaScript. Strukturalne typowanie które oferuje potrafi w miarę nieinwazyjnie pomóc w wykrywaniu pułapek, w które wpadlibyśmy, pisząc w czystym JS. Co jednak w przypadku gdy coś kwacze jak kaczka ale nią nie jest? Czy da się zabezpieczyć developera przed pomieszaniem dwóch różnych jednostek, które w historii doprowadziły do nie jednej katastrofy? Na prelekcji przejdziemy przez różne case study i zastanowimy się jak pomóc TypeScriptowi w ostrzeganiu nas przed pułapkami których nie zawsze da się uniknąć w pierwotnym typowaniu strukturalnym.

TypeScript na przestrzeni lat udowodnił że da się okiełznać dużą część problemów wynikających z dynamiczności języka JavaScript. Strukturalne typowanie które oferuje potrafi w miarę nieinwazyjnie pomóc w wykrywaniu pułapek, w które wpadlibyśmy, pisząc w czystym JS. Co jednak w przypadku gdy coś kwacze jak kaczka ale nią nie jest? Czy da się zabezpieczyć developera przed pomieszaniem dwóch różnych jednostek, które w historii doprowadziły do nie jednej katastrofy? Na prelekcji przejdziemy przez różne case study i zastanowimy się jak pomóc TypeScriptowi w ostrzeganiu nas przed pułapkami których nie zawsze da się uniknąć w pierwotnym typowaniu strukturalnym.

Anzeige
Anzeige

Weitere Verwandte Inhalte

Weitere von The Software House (20)

Aktuellste (20)

Anzeige

Typowanie nominalne w TypeScript

  1. 1. Nominalne typowanie w TypeScript Wiktor Toporek
  2. 2. O mnie
  3. 3. 💼 Senior Frontend Developer w The Software House O mnie
  4. 4. 💼 Senior Frontend Developer w The Software House Fullstack O mnie
  5. 5. 💼 Senior Frontend Developer w The Software House Fullstack Elm O mnie
  6. 6. 💼 Senior Frontend Developer w The Software House Fullstack Elm λ Functional Programming O mnie
  7. 7. 💼 Senior Frontend Developer w The Software House Fullstack Elm λ Functional Programming 🪨 Static typing O mnie
  8. 8. const testInvoice = { positions: [ { productName: "JavaScript: The Good Parts", price: 100 }, { productName: "CSS in Depth", price: "169.99" }, { productName: "Functional Programming in JavaScript", prince: 150.50 }, ], } function getInvoiceTotal(invoice) { return invoice.positions.reduce( (lastTotal, currentPosition) => lastTotal + currentPosition.price, 0 ); }
  9. 9. const testInvoice = { positions: [ { productName: "JavaScript: The Good Parts", price: 100 }, { productName: "CSS in Depth", price: "169.99" }, { productName: "Functional Programming in JavaScript", prince: 150.50 }, ], } function getInvoiceTotal(invoice) { return invoice.positions.reduce( (lastTotal, currentPosition) => lastTotal + currentPosition.price, 0 ); } getInvoiceTotal(testInvoice) ===
  10. 10. const testInvoice = { positions: [ { productName: "JavaScript: The Good Parts", price: 100 }, { productName: "CSS in Depth", price: "169.99" }, { productName: "Functional Programming in JavaScript", prince: 150.50 }, ], } function getInvoiceTotal(invoice) { return invoice.positions.reduce( (lastTotal, currentPosition) => lastTotal + currentPosition.price, 0 ); } getInvoiceTotal(testInvoice) === '100169.99undefined'
  11. 11. const testInvoice = { positions: [ { productName: "JavaScript: The Good Parts", price: 100 }, { productName: "CSS in Depth", price: "169.99" }, { productName: "Functional Programming in JavaScript", prince: 150.50 }, ], } function getInvoiceTotal(invoice) { return invoice.positions.reduce( (lastTotal, currentPosition) => lastTotal + currentPosition.price, 0 ); } getInvoiceTotal(testInvoice) === '100169.99undefined'
  12. 12. const testInvoice = { positions: [ { productName: "JavaScript: The Good Parts", price: 100 }, { productName: "CSS in Depth", price: "169.99" }, { productName: "Functional Programming in JavaScript", prince: 150.50 }, ], } function getInvoiceTotal(invoice) { return invoice.positions.reduce( (lastTotal, currentPosition) => lastTotal + currentPosition.price, 0 ); } getInvoiceTotal(testInvoice) === '100169.99undefined'
  13. 13. const testInvoice = { positions: [ { productName: "JavaScript: The Good Parts", price: 100 }, { productName: "CSS in Depth", price: "169.99" }, { productName: "Functional Programming in JavaScript", prince: 150.50 }, ], } function getInvoiceTotal(invoice) { return invoice.positions.reduce( (lastTotal, currentPosition) => lastTotal + currentPosition.price, 0 ); } getInvoiceTotal(testInvoice) === '100169.99undefined'
  14. 14. type Invoice = { positions: InvoicePosition[]; }
  15. 15. type Invoice = { positions: InvoicePosition[]; } type InvoicePosition = { productName: string; price: number; }
  16. 16. type Invoice = { positions: InvoicePosition[]; } type InvoicePosition = { productName: string; price: number; } const testInvoice: Invoice = { positions: [ { productName: "JavaScript: The Good Parts", price: 100 }, { productName: "CSS in Depth", price: "169.99" }, { productName: "Functional Programming in JavaScript", prince: 150.50 }, ], }
  17. 17. type Invoice = { positions: InvoicePosition[]; } type InvoicePosition = { productName: string; price: number; } const testInvoice: Invoice = { positions: [ { productName: "JavaScript: The Good Parts", price: 100 }, { productName: "CSS in Depth", price: "169.99" }, { productName: "Functional Programming in JavaScript", prince: 150.50 }, ], }
  18. 18. type Invoice = { positions: InvoicePosition[]; } type InvoicePosition = { productName: string; price: number; } const testInvoice: Invoice = { positions: [ { productName: "JavaScript: The Good Parts", price: 100 }, { productName: "CSS in Depth", price: "169.99" }, { productName: "Functional Programming in JavaScript", prince: 150.50 }, ], }
  19. 19. OO w JS
  20. 20. function Person(first, last, age, eyecolor) { this.firstName = first; this.lastName = last; this.age = age; this.eyeColor = eyecolor; } Person.prototype.name = function() { return this.firstName + " " + this.lastName; };
  21. 21. Object.prototype function Person(first, last, age, eyecolor) { this.firstName = first; this.lastName = last; this.age = age; this.eyeColor = eyecolor; } Person.prototype.name = function() { return this.firstName + " " + this.lastName; };
  22. 22. class Book { constructor( public id: string, public name: string, public isbn?: string ) {} }
  23. 23. class Book { constructor( public id: string, public name: string, public isbn?: string ) {} } class User { favoriteBooks: Book[] = []; constructor( public id: string, public name: string, public email: string ) {} addFavoriteBook(book: Book) { this.favoriteBooks.push(book); } }
  24. 24. class Book { constructor( public id: string, public name: string, public isbn?: string ) {} } class User { favoriteBooks: Book[] = []; constructor( public id: string, public name: string, public email: string ) {} addFavoriteBook(book: Book) { this.favoriteBooks.push(book); } } const user1 = new User("123-123-123", "John Doe", "john@doe.com"); const someBook = new Book("12345", "JavaScript: The Good Parts");
  25. 25. const user1 = new User("123-123-123", "John Doe", "john@doe.com"); const someBook = new Book("12345", "JavaScript: The Good Parts");
  26. 26. const user1 = new User("123-123-123", "John Doe", "john@doe.com"); const someBook = new Book("12345", "JavaScript: The Good Parts");
  27. 27. const user1 = new User("123-123-123", "John Doe", "john@doe.com"); const someBook = new Book("12345", "JavaScript: The Good Parts"); user1.addFavoriteBook(
  28. 28. const user1 = new User("123-123-123", "John Doe", "john@doe.com"); const someBook = new Book("12345", "JavaScript: The Good Parts"); user1.addFavoriteBook(s s s s s s s s );
  29. 29. const user1 = new User("123-123-123", "John Doe", "john@doe.com"); const someBook = new Book("12345", "JavaScript: The Good Parts"); user1.addFavoriteBook(s s s s s s s s ); ✅
  30. 30. const user1 = new User("123-123-123", "John Doe", "john@doe.com"); const someBook = new Book("12345", "JavaScript: The Good Parts"); user1.addFavoriteBook(s s s s s s s s );
  31. 31. const user1 = new User("123-123-123", "John Doe", "john@doe.com"); const someBook = new Book("12345", "JavaScript: The Good Parts"); user1.addFavoriteBook(
  32. 32. const user1 = new User("123-123-123", "John Doe", "john@doe.com"); const someBook = new Book("12345", "JavaScript: The Good Parts"); user1.addFavoriteBook("12345");
  33. 33. const user1 = new User("123-123-123", "John Doe", "john@doe.com"); const someBook = new Book("12345", "JavaScript: The Good Parts"); user1.addFavoriteBook(3.14);
  34. 34. const user1 = new User("123-123-123", "John Doe", "john@doe.com"); const someBook = new Book("12345", "JavaScript: The Good Parts"); user1.addFavoriteBook(undefined);
  35. 35. const user1 = new User("123-123-123", "John Doe", "john@doe.com"); const someBook = new Book("12345", "JavaScript: The Good Parts"); user1.addFavoriteBook(z z z z z );
  36. 36. const user1 = new User("123-123-123", "John Doe", "john@doe.com"); const someBook = new Book("12345", "JavaScript: The Good Parts"); user1.addFavoriteBook(z z z z z );
  37. 37. const user1 = new User("123-123-123", "John Doe", "john@doe.com"); const someBook = new Book("12345", "JavaScript: The Good Parts"); user1.addFavoriteBook(z z z z z );
  38. 38. const user1 = new User("123-123-123", "John Doe", "john@doe.com"); const someBook = new Book("12345", "JavaScript: The Good Parts"); user1.addFavoriteBook(
  39. 39. const user1 = new User("123-123-123", "John Doe", "john@doe.com"); const someBook = new Book("12345", "JavaScript: The Good Parts"); user1.addFavoriteBook(user1);
  40. 40. const user1 = new User("123-123-123", "John Doe", "john@doe.com"); const someBook = new Book("12345", "JavaScript: The Good Parts"); user1.addFavoriteBook(user1); 😬
  41. 41. class Book { constructor( public id: string, public name: string, public isbn?: string ) {} } class User { favoriteBooks: Book[] = []; constructor( public id: string, public name: string, public email: string ) {} addFavoriteBook(book: Book) { this.favoriteBooks.push(book); } }
  42. 42. class User { favoriteBooks: Book[] = []; constructor( public id: string, public name: string, public email: string ) {} addFavoriteBook(book: Book) { this.favoriteBooks.push(book); } } type Book = { id: string; name: string; isbn?: string; }
  43. 43. type Book = { id: string; name: string; isbn?: string; } type User = { id: string; name: string; email: string; favoriteBooks: Book[]; addFavoriteBook: (book: Book) => void; }
  44. 44. type Book = { id: string; name: string; isbn?: string; } type User = { id: string; name: string; email: string; favoriteBooks: Book[]; addFavoriteBook: (book: Book) => void; } Book
  45. 45. type Book = { id: string; name: string; isbn?: string; } type User = { id: string; name: string; email: string; favoriteBooks: Book[]; addFavoriteBook: (book: Book) => void; } Book User
  46. 46. type Book = { id: string; name: string; isbn?: string; } type User = { id: string; name: string; email: string; favoriteBooks: Book[]; addFavoriteBook: (book: Book) => void; } Book User
  47. 47. type Book = { id: string; name: string; isbn?: string; } type User = { id: string; name: string; email: string; favoriteBooks: Book[]; addFavoriteBook: (book: Book) => void; } Book User
  48. 48. type Book = { id: string; name: string; isbn?: string; } type User = { id: string; name: string; email: string; favoriteBooks: Book[]; addFavoriteBook: (book: Book) => void; } Book User
  49. 49. type Book = { id: string; name: string; isbn?: string; } type User = { id: string; name: string; email: string; favoriteBooks: Book[]; addFavoriteBook: (book: Book) => void; } Book User
  50. 50. type Book = { id: string; name: string; isbn?: string; } type User = { id: string; name: string; email: string; favoriteBooks: Book[]; addFavoriteBook: (book: Book) => void; } Book ⊄ User
  51. 51. type Book = { id: string; name: string; isbn?: string; } type User = { id: string; name: string; email: string; favoriteBooks: Book[]; addFavoriteBook: (book: Book) => void; } Book User
  52. 52. type Book = { id: string; name: string; isbn?: string; } type User = { id: string; name: string; email: string; favoriteBooks: Book[]; addFavoriteBook: (book: Book) => void; } Book User
  53. 53. class Book { constructor( public id: string, public name: string, public isbn?: string ) {} } class User { favoriteBooks: Book[] = []; constructor( public id: string, public name: string, public email: string ) {} addFavoriteBook(book: Book) { this.favoriteBooks.push(book); } } type Book = { id: string; name: string; isbn?: string; } type User = { id: string; name: string; email: string; favoriteBooks: Book[]; addFavoriteBook: (book: Book) => void; } User Book
  54. 54. class Book { constructor( public id: string, public name: string, public isbn?: string ) {} } class User { favoriteBooks: Book[] = []; constructor( public id: string, public name: string, public email: string ) {} addFavoriteBook(book: Book) { this.favoriteBooks.push(book); } } type Book = { id: string; name: string; isbn?: string; } type User = { id: string; name: string; email: string; favoriteBooks: Book[]; addFavoriteBook: (book: Book) => void; } User Book
  55. 55. class Book { constructor( public id: string, public name: string, public isbn?: string ) {} } class User { favoriteBooks: Book[] = []; constructor( public id: string, public name: string, public email: string ) {} addFavoriteBook(book: Book) { this.favoriteBooks.push(book); } } type Book = { id: string; name: string; isbn?: string; } type User = { id: string; name: string; email: string; favoriteBooks: Book[]; addFavoriteBook: (book: Book) => void; } User Book
  56. 56. class Book { constructor( public id: string, public name: string, public isbn?: string ) {} } class User { favoriteBooks: Book[] = []; constructor( public id: string, public name: string, public email: string ) {} addFavoriteBook(book: Book) { this.favoriteBooks.push(book); } } type Book = { id: string; name: string; isbn?: string; } type User = { id: string; name: string; email: string; favoriteBooks: Book[]; addFavoriteBook: (book: Book) => void; } User Book
  57. 57. class Book { constructor( public id: string, public name: string, public isbn?: string ) {} } class User { favoriteBooks: Book[] = []; constructor( public id: string, public name: string, public email: string ) {} addFavoriteBook(book: Book) { this.favoriteBooks.push(book); } } type Book = { id: string; name: string; isbn?: string; } type User = { id: string; name: string; email: string; favoriteBooks: Book[]; addFavoriteBook: (book: Book) => void; } User ⊂ Book
  58. 58. “if it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck” Duck typing
  59. 59. “if it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck” Duck typing ~JavaScript
  60. 60. Case study 1: 🪪 ID encji
  61. 61. function addBookToUserFavorites(userId: string, bookId: string) { // ... } addBookToUserFavorites( , , ); '716fccd0-73fd-4fbf-be44-7c3176242962' 'a2db2ce5-6ad3-4b01-8f7e-8f435d9d30b2'
  62. 62. function addBookToUserFavorites(userId: string, bookId: string) { // ... } addBookToUserFavorites( , , ); '716fccd0-73fd-4fbf-be44-7c3176242962' 'a2db2ce5-6ad3-4b01-8f7e-8f435d9d30b2'
  63. 63. Nazwane parametry
  64. 64. function addBookToUserFavorites(userId: string, bookId: string) { // ... }
  65. 65. function addBookToUserFavorites(userId: string, bookId: string) { // ... } function addBookToUserFavorites({ userId, bookId }: { userId: string, bookId: string }) { // ... }
  66. 66. function addBookToUserFavorites(userId: string, bookId: string) { // ... } function addBookToUserFavorites({ userId, bookId }: { userId: string, bookId: string }) { // ... } addBookToUserFavorites({ bookId: 'a2db2ce5-6ad3-4b01-8f7e-8f435d9d30b2', userId: '716fccd0-73fd-4fbf-be44-7c3176242962', );
  67. 67. Value Object
  68. 68. class UserId { constructor(public id: string) {} } class BookId { constructor(public id: string) {} }
  69. 69. class UserId { constructor(public id: string) {} } class BookId { constructor(public id: string) {} } function addBookToUserFavorites(userId: UserId, bookId: BookId) { // ... }
  70. 70. class UserId { constructor(public id: string) {} } class BookId { constructor(public id: string) {} } function addBookToUserFavorites(userId: UserId, bookId: BookId) { // ... } const userId = new UserId('716fccd0-73fd-4fbf-be44-7c3176242962'); const bookId = new BookId('a2db2ce5-6ad3-4b01-8f7e-8f435d9d30b2'); addBookToUserFavorites(bookId, userId);
  71. 71. class UserId { constructor(public id: string) {} } class BookId { constructor(public id: string) {} } function addBookToUserFavorites(userId: UserId, bookId: BookId) { // ... } const userId = new UserId('716fccd0-73fd-4fbf-be44-7c3176242962'); const bookId = new BookId('a2db2ce5-6ad3-4b01-8f7e-8f435d9d30b2'); addBookToUserFavorites(bookId, userId);
  72. 72. class UserId { constructor(public id: string) {} } class BookId { constructor(public id: string) {} } private type = 'user'; function addBookToUserFavorites(userId: UserId, bookId: BookId) { // ... } const userId = new UserId('716fccd0-73fd-4fbf-be44-7c3176242962'); const bookId = new BookId('a2db2ce5-6ad3-4b01-8f7e-8f435d9d30b2'); addBookToUserFavorites(bookId, userId);
  73. 73. class UserId { constructor(public id: string) {} } class BookId { constructor(public id: string) {} } private type = 'user'; private type = 'book'; function addBookToUserFavorites(userId: UserId, bookId: BookId) { // ... } const userId = new UserId('716fccd0-73fd-4fbf-be44-7c3176242962'); const bookId = new BookId('a2db2ce5-6ad3-4b01-8f7e-8f435d9d30b2'); addBookToUserFavorites(bookId, userId);
  74. 74. class UserId { constructor(public id: string) {} } class BookId { constructor(public id: string) {} } private type = 'user'; private type = 'book'; function addBookToUserFavorites(userId: UserId, bookId: BookId) { // ... } const userId = new UserId('716fccd0-73fd-4fbf-be44-7c3176242962'); const bookId = new BookId('a2db2ce5-6ad3-4b01-8f7e-8f435d9d30b2'); addBookToUserFavorites(bookId, userId);
  75. 75. Case study 2: 🕑 Czas
  76. 76. async function delayedHelloWorld() { await delay(1000); console.log('Hello World'); }
  77. 77. async function delayedHelloWorld() { await delay(1000); console.log('Hello World'); } const delay = (timeInterval: number) => new Promise( resolve => setTimeout(resolve, timeInterval) );
  78. 78. async function delayedHelloWorld() { await delay(1000); console.log('Hello World'); } const delay = (timeInterval: number) => new Promise( resolve => setTimeout(resolve, timeInterval) ); async function delayedHelloWorld() { await delay(1); console.log('Hello World'); }
  79. 79. https://www.simscale.com/blog/nasa-mars-climate-orbiter-metric/
  80. 80. async function delayedHelloWorld() { await delay(1000); console.log('Hello World'); } async function delayedHelloWorld() { await delay(1); console.log('Hello World'); } const delay = (timeInterval: number) => new Promise( resolve => setTimeout(resolve, timeInterval) );
  81. 81. async function delayedHelloWorld() { await delay(1000); console.log('Hello World'); } async function delayedHelloWorld() { await delay(1); console.log('Hello World'); } const delay = (timeInterval: number) => new Promise( resolve => setTimeout(resolve, timeInterval) ); const delay = (timeIntervalInMilliseconds: number) => new Promise( resolve => setTimeout(resolve, timeInterval) );
  82. 82. Value Object!
  83. 83. class TimeInterval { }
  84. 84. class TimeInterval { } private value: number;
  85. 85. class TimeInterval { } private value: number; private constructor(milliseconds: number) { this.value = milliseconds; }
  86. 86. class TimeInterval { } private value: number; private constructor(milliseconds: number) { this.value = milliseconds; } static fromMilliseconds(milliseconds: number) { return new TimeInterval(milliseconds); }
  87. 87. class TimeInterval { } private value: number; private constructor(milliseconds: number) { this.value = milliseconds; } static fromMilliseconds(milliseconds: number) { return new TimeInterval(milliseconds); } static fromSeconds(seconds: number) { return new TimeInterval(seconds * 1000); }
  88. 88. class TimeInterval { } private value: number; private constructor(milliseconds: number) { this.value = milliseconds; } static fromMilliseconds(milliseconds: number) { return new TimeInterval(milliseconds); } static fromSeconds(seconds: number) { return new TimeInterval(seconds * 1000); } toMilliseconds() { return this.value; }
  89. 89. class TimeInterval { } private value: number; private constructor(milliseconds: number) { this.value = milliseconds; } static fromMilliseconds(milliseconds: number) { return new TimeInterval(milliseconds); } static fromSeconds(seconds: number) { return new TimeInterval(seconds * 1000); } toMilliseconds() { return this.value; } toSeconds() { return this.value / 1000; }
  90. 90. const delay = (timeInterval: TimeInterval) => new Promise( resolve => setTimeout(resolve, timeInterval.toMilliseconds()) ); async function delayedHelloWorld() { await delay(1); console.log('Hello World'); }
  91. 91. const delay = (timeInterval: TimeInterval) => new Promise( resolve => setTimeout(resolve, timeInterval.toMilliseconds()) ); async function delayedHelloWorld() { await delay(1); console.log('Hello World'); }
  92. 92. const delay = (timeInterval: TimeInterval) => new Promise( resolve => setTimeout(resolve, timeInterval.toMilliseconds()) ); async function delayedHelloWorld() { await delay(1); console.log('Hello World'); }
  93. 93. const delay = (timeInterval: TimeInterval) => new Promise( resolve => setTimeout(resolve, timeInterval.toMilliseconds()) ); async function delayedHelloWorld() { await delay(1); console.log('Hello World'); } async function delayedHelloWorld() { await delay(TimeInterval.fromSeconds(1)); console.log('Hello World'); }
  94. 94. Case study 3: 💵 Pieniądze
  95. 95. { price: 100 }
  96. 96. { price: 100 }
  97. 97. { price: 100 }
  98. 98. { price: 100 }
  99. 99. Value Object!
  100. 100. class Money { constructor( public amount: number, public currency: string, ) {} }
  101. 101. class Money { constructor( public amount: number, public currency: string, ) {} } add(money: Money): Money { return new Money(this.amount + money.amount, this.currency); }
  102. 102. class Money { constructor( public amount: number, public currency: string, ) {} } add(money: Money): Money { if (this.currency !== money.currency) { throw new Error("Cannot add different currencies"); } return new Money(this.amount + money.amount, this.currency); }
  103. 103. class Money<T extends string> { constructor( public amount: number, public currency: T, ) {} add(money: Money<T>): Money<T> { return new Money(this.amount + money.amount, this.currency); } }
  104. 104. class Money<T extends string> { constructor( public amount: number, public currency: T, ) {} add(money: Money<T>): Money<T> { return new Money(this.amount + money.amount, this.currency); } } const somePLNs = new Money(100, 'PLN'); const someEuros = new Money(100, 'EUR'); somePLNs.add(someEuros);
  105. 105. class Money<T extends string> { constructor( public amount: number, public currency: T, ) {} add(money: Money<T>): Money<T> { return new Money(this.amount + money.amount, this.currency); } } const somePLNs = new Money(100, 'PLN'); const someEuros = new Money(100, 'EUR'); somePLNs.add(someEuros);
  106. 106. class Money constructor( public amount: number, public currency: T, ) {} add(money: Money<T>): Money<T> { return new Money(this.amount + money.amount, this.currency); } } const somePLNs: Money<string> = new Money(100, 'PLN'); const someEuros = new Money(100, 'EUR'); somePLNs.add(someEuros); <T e e e e e e e e e e e e e > >
  107. 107. class Money constructor( public amount: number, public currency: T, ) {} add(money: Money<T>): Money<T> { return new Money(this.amount + money.amount, this.currency); } } const somePLNs: Money<string> = new Money(100, 'PLN'); const someEuros = new Money(100, 'EUR'); somePLNs.add(someEuros);
  108. 108. class Money constructor( public amount: number, public currency: T, ) {} add(money: Money<T>): Money<T> { return new Money(this.amount + money.amount, this.currency); } } const somePLNs: Money<string> = new Money(100, 'PLN'); const someEuros = new Money(100, 'EUR'); somePLNs.add(someEuros); <in out T extends string> {
  109. 109. class Money constructor( public amount: number, public currency: T, ) {} add(money: Money<T>): Money<T> { return new Money(this.amount + money.amount, this.currency); } } const somePLNs: Money<string> = new Money(100, 'PLN'); const someEuros = new Money(100, 'EUR'); somePLNs.add(someEuros); <in out T extends string> {
  110. 110. class Money constructor( public amount: number, public currency: T, ) {} add(money: Money<T>): Money<T> { return new Money(this.amount + money.amount, this.currency); } } const somePLNs: Money<string> = new Money(100, 'PLN'); const someEuros = new Money(100, 'EUR'); somePLNs.add(someEuros); <in out T extends string> {
  111. 111. class Money constructor( public amount: number, public currency: T, ) {} add(money: Money<T>): Money<T> { return new Money(this.amount + money.amount, this.currency); } } const somePLNs = new Money(100, 'PLN' as string); const someEuros = new Money(100, 'EUR' as string); somePLNs.add(someEuros); <T extends string> { <in out T extends string> {
  112. 112. A co z Functional Programming?
  113. 113. Nominal Typing? 🤔
  114. 114. Opaque types
  115. 115. Opaque types Branded types
  116. 116. Opaque types Branded types Nominal types
  117. 117. https://flow.org/en/
  118. 118. https://flow.org/en/docs/types/opaque-types/
  119. 119. Case study 1: 🪪 ID encji
  120. 120. function addBookToUserFavorites(userId: UserId, bookId: BookId) { // ... }
  121. 121. type UserId = string; function addBookToUserFavorites(userId: UserId, bookId: BookId) { // ... }
  122. 122. type UserId = string; function addBookToUserFavorites(userId: UserId, bookId: BookId) { // ... } type BookId = string;
  123. 123. type UserId = string; function addBookToUserFavorites(userId: UserId, bookId: BookId) { // ... } type BookId = string; addBookToUserFavorites( '716fccd0-73fd-4fbf-be44-7c3176242962', 'a2db2ce5-6ad3-4b01-8f7e-8f435d9d30b2', );
  124. 124. TypeScript nie istnieje w runtime
  125. 125. type UserId = string; function addBookToUserFavorites(userId: UserId, bookId: BookId) { // ... } type BookId = string; addBookToUserFavorites( '716fccd0-73fd-4fbf-be44-7c3176242962', 'a2db2ce5-6ad3-4b01-8f7e-8f435d9d30b2', );
  126. 126. type UserId = string; function addBookToUserFavorites(userId: UserId, bookId: BookId) { // ... } type BookId = string; addBookToUserFavorites( '716fccd0-73fd-4fbf-be44-7c3176242962', 'a2db2ce5-6ad3-4b01-8f7e-8f435d9d30b2', ); type UserId = string & { _type: 'user_id' };
  127. 127. type UserId = string; function addBookToUserFavorites(userId: UserId, bookId: BookId) { // ... } type BookId = string; addBookToUserFavorites( '716fccd0-73fd-4fbf-be44-7c3176242962', 'a2db2ce5-6ad3-4b01-8f7e-8f435d9d30b2', ); type UserId = string & { _type: 'user_id' }; type BookId = string & { _type: 'book_id' };
  128. 128. type UserId = string; function addBookToUserFavorites(userId: UserId, bookId: BookId) { // ... } type BookId = string; addBookToUserFavorites( '716fccd0-73fd-4fbf-be44-7c3176242962', 'a2db2ce5-6ad3-4b01-8f7e-8f435d9d30b2', ); type UserId = string & { _type: 'user_id' }; type BookId = string & { _type: 'book_id' };
  129. 129. type UserId = string; function addBookToUserFavorites(userId: UserId, bookId: BookId) { // ... } type BookId = string; addBookToUserFavorites( '716fccd0-73fd-4fbf-be44-7c3176242962', 'a2db2ce5-6ad3-4b01-8f7e-8f435d9d30b2', ); type UserId = string & { _type: 'user_id' }; type BookId = string & { _type: 'book_id' };
  130. 130. type UserId = string; function addBookToUserFavorites(userId: UserId, bookId: BookId) { // ... } type BookId = string; addBookToUserFavorites( '716fccd0-73fd-4fbf-be44-7c3176242962', 'a2db2ce5-6ad3-4b01-8f7e-8f435d9d30b2', ); type UserId = string & { _type: 'user_id' }; type BookId = string & { _type: 'book_id' };
  131. 131. type UserId = string; function addBookToUserFavorites(userId: UserId, bookId: BookId) { // ... } type BookId = string; addBookToUserFavorites( '716fccd0-73fd-4fbf-be44-7c3176242962', 'a2db2ce5-6ad3-4b01-8f7e-8f435d9d30b2', ); type UserId = string & { _type: 'user_id' }; type BookId = string & { _type: 'book_id' }; addBookToUserFavorites( '716fccd0-73fd-4fbf-be44-7c3176242962' as UserId, 'a2db2ce5-6ad3-4b01-8f7e-8f435d9d30b2' as BookId, );
  132. 132. type UserId = string & { _type: 'user_id' };
  133. 133. type UserId = string & { _type: 'user_id' };
  134. 134. type UserId = string & { _type: 'user_id' }; type Property = keyof any;
  135. 135. type UserId = string & { _type: 'user_id' }; type Property = keyof any; | string
  136. 136. type UserId = string & { _type: 'user_id' }; type Property = keyof any; | string object['_type']
  137. 137. type UserId = string & { _type: 'user_id' }; type Property = keyof any; | string object['_type'] | number
  138. 138. type UserId = string & { _type: 'user_id' }; type Property = keyof any; | string object['_type'] | number array[0]
  139. 139. type UserId = string & { _type: 'user_id' }; type Property = keyof any; | string object['_type'] | number array[0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
  140. 140. type UserId = string & { _type: 'user_id' }; type Property = keyof any; | string object['_type'] | number array[0]
  141. 141. type UserId = string & { _type: 'user_id' }; type Property = keyof any; | string object['_type'] | number array[0] | symbol
  142. 142. type UserId = string & { _type: 'user_id' }; type Property = keyof any; | string object['_type'] | number array[0] | symbol object[Symbol('a')] = 'a';
  143. 143. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
  144. 144. https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html
  145. 145. type UserId = string & { _type: 'user_id' };
  146. 146. type UserId = string & { _type: 'user_id' }; declare const Nominal: unique symbol;
  147. 147. type UserId = string & { _type: 'user_id' }; declare const Nominal: unique symbol; type UserId = string & { [Nominal]: 'user_id' };
  148. 148. type UserId = string & { _type: 'user_id' }; declare const Nominal: unique symbol; type UserId = string & { [Nominal]: 'user_id' }; type NominalType<TType, TNominal extends string> = TType & { [Nominal]: TNominal };
  149. 149. type UserId = string & { _type: 'user_id' }; declare const Nominal: unique symbol; type UserId = string & { [Nominal]: 'user_id' }; type NominalType<TType, TNominal extends string> = TType & { [Nominal]: TNominal }; type UserId = NominalType<string, 'user_id'>
  150. 150. type UserId = string & { _type: 'user_id' }; declare const Nominal: unique symbol; type UserId = string & { [Nominal]: 'user_id' }; type NominalType<TType, TNominal extends string> = TType & { [Nominal]: TNominal }; type UserId = NominalType<string, 'user_id'> type BookId = NominalType<string, 'book_id'>
  151. 151. Backend
  152. 152. Backend Frontend
  153. 153. Backend Frontend Kontrakt
  154. 154. Backend Frontend Kontrakt GET /books { id: BookId }[]
  155. 155. Backend Frontend Kontrakt GET /books { id: BookId }[] POST /favorites { id: BookId }
  156. 156. Case study 2: 🕑 Czas
  157. 157. async function delayedHelloWorld() { await delay(1000); console.log('Hello World'); } const delay = (timeInterval: number) => new Promise( resolve => setTimeout(resolve, timeInterval) ); async function delayedHelloWorld() { await delay(1); console.log('Hello World'); }
  158. 158. type TimeInterval = NominalType<number, 'TimeInterval'>;
  159. 159. type TimeInterval = NominalType<number, 'TimeInterval'>; const milliseconds = (howMany: number) => howMany as TimeInterval; const seconds = (howMany: number) => (howMany * 1000) as TimeInterval;
  160. 160. type TimeInterval = NominalType<number, 'TimeInterval'>; const milliseconds = (howMany: number) => howMany as TimeInterval; const seconds = (howMany: number) => (howMany * 1000) as TimeInterval; const toMilliseconds = (timeInterval: TimeInterval) => timeInterval as number; const toSeconds = (timeInterval: TimeInterval) => timeInterval / 1000;
  161. 161. const delay = (timeInterval: TimeInterval) => new Promise( resolve => setTimeout(resolve, toMilliseconds(timeInterval)) );
  162. 162. const delay = (timeInterval: TimeInterval) => new Promise( resolve => setTimeout(resolve, toMilliseconds(timeInterval)) ); async function delayedHelloWorld() { await delay(1); console.log('Hello World'); }
  163. 163. const delay = (timeInterval: TimeInterval) => new Promise( resolve => setTimeout(resolve, toMilliseconds(timeInterval)) ); async function delayedHelloWorld() { await delay(1); console.log('Hello World'); }
  164. 164. const delay = (timeInterval: TimeInterval) => new Promise( resolve => setTimeout(resolve, toMilliseconds(timeInterval)) ); async function delayedHelloWorld() { await delay(1); console.log('Hello World'); }
  165. 165. const delay = (timeInterval: TimeInterval) => new Promise( resolve => setTimeout(resolve, toMilliseconds(timeInterval)) ); async function delayedHelloWorld() { await delay(1); console.log('Hello World'); } async function delayedHelloWorld() { await delay(seconds(1)); console.log('Hello World'); }
  166. 166. Case study 3: 💵 Pieniądze
  167. 167. declare const Nominal: unique symbol; type Money<TCurrency extends string> = number & { [Nominal]: TCurrency };
  168. 168. declare const Nominal: unique symbol; type Money<TCurrency extends string> = number & { [Nominal]: TCurrency }; const moneyInPLN = (amount: number) => amount as Money<'PLN'>; const moneyInEUR = (amount: number) => amount as Money<'EUR'>;
  169. 169. declare const Nominal: unique symbol; type Money<TCurrency extends string> = number & { [Nominal]: TCurrency }; const moneyInPLN = (amount: number) => amount as Money<'PLN'>; const moneyInEUR = (amount: number) => amount as Money<'EUR'>; const somePLNs = moneyInPLN(100); const someEuros = moneyInEUR(100);
  170. 170. declare const Nominal: unique symbol; type Money<TCurrency extends string> = number & { [Nominal]: TCurrency }; const moneyInPLN = (amount: number) => amount as Money<'PLN'>; const moneyInEUR = (amount: number) => amount as Money<'EUR'>; const somePLNs = moneyInPLN(100); const someEuros = moneyInEUR(100); const amount1: Money<'EUR'> = someEuros; const amount2: Money<'EUR'> = somePLNs;
  171. 171. declare const Nominal: unique symbol; type Money<TCurrency extends string> = number & { [Nominal]: TCurrency }; const moneyInPLN = (amount: number) => amount as Money<'PLN'>; const moneyInEUR = (amount: number) => amount as Money<'EUR'>; const somePLNs = moneyInPLN(100); const someEuros = moneyInEUR(100); const amount1: Money<'EUR'> = someEuros; const amount2: Money<'EUR'> = somePLNs;
  172. 172. declare const Nominal: unique symbol; type Money<TCurrency extends string> = number & { [Nominal]: TCurrency }; const moneyInPLN = (amount: number) => amount as Money<'PLN'>; const moneyInEUR = (amount: number) => amount as Money<'EUR'>; const somePLNs = moneyInPLN(100); const someEuros = moneyInEUR(100); const amount1: Money<'EUR'> = someEuros; const amount2: Money<'EUR'> = somePLNs;
  173. 173. const amount1: Money<'EUR'> = someEuros; const amount2: Money<'PLN'> = somePLNs; const sum = amount1 + amount2;
  174. 174. const amount1: Money<'EUR'> = someEuros; const amount2: Money<'PLN'> = somePLNs; const sum = amount1 + amount2; const sum = 1 + 1;
  175. 175. const amount1: Money<'EUR'> = someEuros; const amount2: Money<'PLN'> = somePLNs; const sum = amount1 + amount2; const sum = 1 + 1; const sum = {} + {};
  176. 176. const amount1: Money<'EUR'> = someEuros; const amount2: Money<'PLN'> = somePLNs; const sum = amount1 + amount2; const sum = 1 + 1; const sum = {} + {};
  177. 177. const amount1: Money<'EUR'> = someEuros; const amount2: Money<'PLN'> = somePLNs; const sum = amount1 + amount2; const sum = 1 + 1; const sum = {} + {};
  178. 178. declare const Nominal: unique symbol; type Money<TCurrency extends string> = number & { [Nominal]: TCurrency };
  179. 179. declare const Nominal: unique symbol; type Money<TCurrency extends string> = number & { [Nominal]: TCurrency }; declare const Nominal: unique symbol; type Money<TCurrency extends string> = Number & { [Nominal]: TCurrency };
  180. 180. declare const Nominal: unique symbol; type Money<TCurrency extends string> = number & { [Nominal]: TCurrency }; declare const Nominal: unique symbol; type Money<TCurrency extends string> = Number & { [Nominal]: TCurrency }; const moneyInPLN = (amount: number) => amount as Number as Money<'PLN'>; const moneyInEUR = (amount: number) => amount as Number as Money<'EUR'>;
  181. 181. declare const Nominal: unique symbol; type Money<TCurrency extends string> = number & { [Nominal]: TCurrency }; declare const Nominal: unique symbol; type Money<TCurrency extends string> = Number & { [Nominal]: TCurrency }; const moneyInPLN = (amount: number) => amount as Number as Money<'PLN'>; const moneyInEUR = (amount: number) => amount as Number as Money<'EUR'>; const sum = someEuros + somePLNs;
  182. 182. declare const Nominal: unique symbol; type Money<TCurrency extends string> = number & { [Nominal]: TCurrency }; declare const Nominal: unique symbol; type Money<TCurrency extends string> = Number & { [Nominal]: TCurrency }; const moneyInPLN = (amount: number) => amount as Number as Money<'PLN'>; const moneyInEUR = (amount: number) => amount as Number as Money<'EUR'>; const sum = someEuros + somePLNs;
  183. 183. declare const Nominal: unique symbol; type Money<TCurrency extends string> = number & { [Nominal]: TCurrency }; declare const Nominal: unique symbol; type Money<TCurrency extends string> = Number & { [Nominal]: TCurrency }; const moneyInPLN = (amount: number) => amount as Number as Money<'PLN'>; const moneyInEUR = (amount: number) => amount as Number as Money<'EUR'>; const sum = someEuros + somePLNs;
  184. 184. function addMoney<TMoney extends Money<string>>(amount1: TMoney, amount2: TMoney): TMoney { return amount1 as any + amount2; }
  185. 185. function addMoney<TMoney extends Money<string>>(amount1: TMoney, amount2: TMoney): TMoney { return amount1 as any + amount2; } const sum = addMoney(somePLNs, someEuros)
  186. 186. function addMoney<TMoney extends Money<string>>(amount1: TMoney, amount2: TMoney): TMoney { return amount1 as any + amount2; } const sum = addMoney(somePLNs, someEuros)
  187. 187. function addMoney<TMoney extends Money<string>>(amount1: TMoney, amount2: TMoney): TMoney { return amount1 as any + amount2; } const sum = addMoney(somePLNs, someEuros)
  188. 188. Podsumowanie
  189. 189. Typowanie strukturalne + OO bywa pułapką
  190. 190. Typowanie strukturalne + OO bywa pułapką user1.addFavoriteBook(user1);
  191. 191. Możliwa jest symulacja typów nominalnych w TS
  192. 192. Możliwa jest symulacja typów nominalnych w TS declare const Nominal: unique symbol;
  193. 193. Możliwa jest symulacja typów nominalnych w TS declare const Nominal: unique symbol; type NominalType<TType, TNominal extends string> = TType & { [Nominal]: TNominal };
  194. 194. Możemy dodać prymitywnym typom nominały
  195. 195. Możemy dodać prymitywnym typom nominały type UserId = NominalType<string, 'user_id'> type BookId = NominalType<string, 'book_id'>
  196. 196. Case study z pieniędzmi to być może nie najlepsze zastosowanie,
  197. 197. Case study z pieniędzmi to być może nie najlepsze zastosowanie, ale warto wiedzieć że w TS istnieje masa sztuczek ;-)
  198. 198. Case study z pieniędzmi to być może nie najlepsze zastosowanie, ale warto wiedzieć że w TS istnieje masa sztuczek ;-) Enkapsulacja operacji na prymitywnych typach
  199. 199. Case study z pieniędzmi to być może nie najlepsze zastosowanie, ale warto wiedzieć że w TS istnieje masa sztuczek ;-) Enkapsulacja operacji na prymitywnych typach Opaque types
  200. 200. Typy nominalne mogą czuwać nad liczbami i jednostkami
  201. 201. Typy nominalne mogą czuwać nad liczbami i jednostkami async function delayedHelloWorld() { await delay(seconds(1)); console.log('Hello World'); }
  202. 202. Typy nominalne mogą czuwać nad liczbami i jednostkami async function delayedHelloWorld() { await delay(seconds(1)); console.log('Hello World'); } Prymitywne typy pod spodem
  203. 203. Typy nominalne mogą czuwać nad liczbami i jednostkami async function delayedHelloWorld() { await delay(seconds(1)); console.log('Hello World'); } Prymitywne typy pod spodem Terser?
  204. 204. Typy nominalne mogą czuwać nad liczbami i jednostkami async function delayedHelloWorld() { await delay(seconds(1)); console.log('Hello World'); } Prymitywne typy pod spodem Terser? Inlining przy kompilacji
  205. 205. Typy nominalne mogą czuwać nad liczbami i jednostkami async function delayedHelloWorld() { await delay(seconds(1)); console.log('Hello World'); } Prymitywne typy pod spodem Terser? Inlining przy kompilacji async function delayedHelloWorld() { await delay(1000); console.log('Hello World'); }
  206. 206. Dziękuje za uwagę! Wiktor Toporek Senior Frontend Developer ✉ wiktor.toporek@tsh.io Twitter: @vViktorPL tsh.io ✉ wtoporek@gmail.com tsh.io/programowanko
  207. 207. Dziękuje za uwagę! Wiktor Toporek Senior Frontend Developer ✉ wiktor.toporek@tsh.io Twitter: @vViktorPL tsh.io Q & A ✉ wtoporek@gmail.com tsh.io/programowanko

×