Upgrade to Pro — share decks privately, control downloads, hide ads and more …

從 Functional Programming 的角度看 2021 的 TypeScript

9b753d898d93aae8bd163db5c420a1ae?s=47 Jerry Hong
September 23, 2021

從 Functional Programming 的角度看 2021 的 TypeScript

如今的 Typescript 相較剛出來時已經熟成許多,現在是否真的值得在開發上使用 Typescript?從 Functional Programming 的角度來說 Typescript 有什麼缺陷?使用 Typescript 需要注意哪些問題?使用 Typescript 能帶來什麼優勢?什麼情況下適合導入 Typescript?

9b753d898d93aae8bd163db5c420a1ae?s=128

Jerry Hong

September 23, 2021
Tweet

Transcript

  1. 從 Functional Programming 的⾓度 看 2021 的 TypeScript λ TS

  2. Functional Programming 跟 Type 有什麼關係?

  3. Functional Programming 三個核⼼ |> Function |> Composition |> Type

  4. Type in Functional Programming |> Type 就像是集合 (Set)

  5. type A = 1 type B = 1 | 2

    | 3 type C = 1 | 2 | 3 | ... // write all number // C = number type D = '' | 'a' | 'b' | ... // write all string // D = string type E = true | false // E = boolean Type 就像是集合 (Set) TS
  6. Type in Functional Programming |> Type 就像是集合 (Set) |> Type

    幫助我們定義 Function
  7. Function 是什麼? 1 2 3 A B C X Y

    f(X) Pure f : X → Y
  8. const add1 = x => 1 + x add1(2) //

    3 ⽤ JS 定義⼀個 Add1 function JS add1('abc') // '1abc' type Add1 = (x: number) => number
  9. add1 原本的意義 1 2 3 Number Add1(x)

  10. add1 沒有 type 狀態下的意義不明 1 '2' 2 '12' ? ?

    Add1(x)
  11. const add1 = x => 1 + x add1(2) //

    3 定義不明 add1('abc') // '1abc' type Add1 = (x: number) => number JS
  12. Type 幫助我們定 義 function TS const add1: Add1 = x

    => 1 + x add1(2) // 3 add1('abc') // Type Error type Add1 = (x: number) => number
  13. Jerry Hong Tech Leader | Website: blog.jerry-hong.com Branch8 2019 ModernWeb

    speaker 2018 FEDC organiser 2017 JSDC.tw speaker 2017 RxJS 30天鐵⼈賽冠軍 2016 JSDC.tw speaker
  14. 就⼀般開發的⾓度 Type 帶來了哪些優勢 ?

  15. Type 帶來的優勢 |> 型別檢查 (type check) 可以在 編譯時期 (compile time)

    檢測出很多 潛在的 bugs |> 型別 (type) 能提供最新的⽂件 (up-to-date document) |> 型別 (type) 可以驅動開發 (Type-Driven Development)
  16. Type 的優勢 !== TypeScript 的優勢

  17. 回顧 TypeScript 的發展歷史

  18. TypeScript Release History |> 1.4 - Union Type, Type Guard,

    Type Aliases |> 1.6 - Intersection Type, User-defined Type Guard Function |> 1.8 - Constraints Generics |> 2.0 - undefined(null)-aware Types, Control Flow Based Type Analysis, Discriminated Union Types, never type |> 2.1 - keyof and lookup types, mapped types |> 2.8 - Conditional Types |> 4.0 - Variadic Tuple Types, Labeled Tuple Elements |> 4.1 - Template Literal Types, Key Remapping in Mapped Types, Recursive Conditional Types |> 4.4 - Control Flow Analysis of Aliased Conditions and Discriminants
  19. Typescript 的優缺點

  20. TypeScript 的優點 |> 許多 JS Library 採⽤ TypeScript

  21. 採⽤ TypeScript 的 Library |> redux |> graphql-js |> apollo-client

    |> rxjs |> xstate
  22. Github 2020 - octoverse

  23. TypeScript 的優點 |> 許多 JS Library 採⽤ TypeScript |> Editor

    ⽀援良好
  24. VS Code |> ⽬前最多⼈使⽤的 Editor |> 原⽣⽀援 TypeScript |> Go

    to definition |> Auto-complete |> Refactoring |> Renaming |> ...
  25. TypeScript 的優點 |> 許多 JS Library 採⽤ TypeScript |> Editor

    ⽀援良好 |> 前後端可共⽤, 且⽣態⽀援好
  26. 前後端可共⽤, 且⽣態⽀援好 |> 前後端可共⽤ Type 甚⾄ Validation |> Library 及

    Tools |> prisma |> typeorm |> apollo-server |> graphql-code-generator |> zod |> ts-json-validator
  27. TypeScript 的優點 |> 許多 JS Library 採⽤ TypeScript |> Editor

    ⽀援良好 |> 前後端可共⽤, 且⽣態⽀援好 |> Type system 圖靈完備 (Turing Completeness)
  28. Type system 圖靈完備 (Turing Completeness) | > 可以⽤ Type System

    做任何運算 | > 可以做 Type Level Programming | > 定義⾃然數 | > 可以做到⼀些 Dependent type 的效果 | > Type check is undecidable | > Implement Collatz | > The Undecidability of the Generalized Collatz Problem
  29. TypeScript 的優點 |> 許多 JS Library 採⽤ TypeScript |> Editor

    ⽀援良好 |> 前後端可共⽤, 且⽣態⽀援好 |> Type system 圖靈完備 (Turing Completeness) |> 在非嚴格模式及可⽤ Any 的狀況下, 對 JS 的開發者來說相對好上⼿
  30. TypeScript 的缺點 |> 不成熟 |> Third Party Library Issue

  31. Third Party Library Issue |> Type 定義錯誤 |> 仍有許多 Library

    是由 JS 撰寫, 只有維護⼀份型別定義檔 (d.ts), 可 能存在 Type 定義錯誤 |> TypeScript 本⾝的 unsound, 非嚴格模式 以及 any 被濫⽤的可能, 導 致 Type Check 若有似無 |> Config 設定不⼀致, 導致奇怪的 Bug |> 嚴格模式下 xstate 的 assign
  32. TypeScript 的缺點 |> 不成熟 |> Third Party Library Issue |>

    沒有真正的 Tagged Union Type |> 沒有 Pattern Match
  33. type Circle = { kind: 'circle' radius: number } type

    Square = { kind: 'square' side: number } type Shape = Circle | Square Tagged Union Type |> Sum type |> 必須加上識別屬性 TS
  34. type Circle = { kind: 'circle' radius: number } type

    Square = { kind: 'square' side: number } type Shape = Circle | Square Tagged Union Type |> Sum type |> 必須加上識別屬性 |> 需要透過屬性判定, 來完成 type guard |> 沒有 Pattern Match TS function f(s: Shape) { if (s.kind === 'circle') { return s.radius } if (s.kind === 'square') { return s.side } }
  35. TS type Circle = { kind: 'circle', radius: number }

    type Square = { kind: 'square', side: number } type Shape = Circle | Square function f(s: Shape) { if (s.kind === 'circle') { return s.radius } if (s.kind === 'square') { return s.side } } data Shape = Circle { radius :: Float } | Square { side :: Float } f :: Shape -> Float f Circle { radius } = radius f Square { side } = side
  36. TS type Circle = { kind: 'circle', radius: number }

    type Square = { kind: 'square', side: number } type Shape = Circle | Square function f(s: Shape) { if (s.kind === 'circle') { return s.radius } if (s.kind === 'square') { return s.side } } type Shape = | Circle of radius: float | Square of side: float let f shape = match shape with | Circle(radius = r) -> r | Square(side = s) -> s
  37. TypeScript 的缺點 |> 不成熟 |> Third Party Library Issue |>

    沒有真正的 Tagged Union Type |> 沒有 Pattern Match |> 沒有 New Type
  38. type email = string type FindUser = (email: email) =>

    User const findUser: FindUser = (email) => {} findUser('www') // no error 沒有 New Type |> 只有 type alias TS
  39. 沒有 New Type |> 只有 type alias |> 本質上是無法定義 ⼀個無限的集合

    (如 email) String Email #33290
  40. TypeScript 的缺點 |> 不成熟 |> Third Party Library Issue |>

    沒有真正的 Tagged Union Type |> 沒有 Pattern Match |> 沒有 New Type |> 難以理解的 Type Error Message
  41. type A = string | number function f(a: A) {}

    f(false) 難以理解的 Type Error Message TS
  42. TypeScript 的缺點 |> 不成熟 |> Third party library issue |>

    沒有真正的 Tagged Union Type |> 沒有 Pattern Match |> 沒有 New Type |> 難以理解的 Type Error Message |> 缺陷 |> Unsoundness
  43. let x: number | null = 12 function nullX() {

    x = null } nullX() x.toFixed(1) // no error Unsoundness TS ⽬前還有 72 個 open unsound issue 在 github
  44. TypeScript 的缺點 |> 不成熟 |> Third Party Library Issue |>

    沒有真正的 Tagged Union Type |> 沒有 Pattern Match |> 沒有 New Type |> 難以理解的 Type Error Message |> 缺陷 |> Unsoundness |> Syntax 混亂
  45. export function createMachine< TContext, TEvent extends EventObject = AnyEventObject, TTypestate

    extends Typestate<TContext> = { value: any; context: TContext } >( config: MachineConfig<TContext, any, TEvent>, options?: Partial<MachineOptions<TContext, TEvent >> ): StateMachine<TContext, any, TEvent, TTypestate> { return new StateNode<TContext, any, TEvent, TTypestate>( config, options ) as StateMachine<TContext, any, TEvent, TTypestate>; } Syntax 混亂 |> 容易把 Type 跟 Code 混在⼀起 TS
  46. type Diff<T, U> = T extends U ? never :

    T; // Remove types from T that are assignable to U type Filter<T, U> = T extends U ? T : never; // Remove types from T that are not assignable to U type Result1 = Diff<'1' | '2' | '3', '3'> // '1' | '2' type Result2 = Filter<'1' | '2' | '3', '3'> // '3' type UnionToIntersection<U> = ( U extends never ? never : (arg: U) => never ) extends (arg: infer I) => void ? I : never; type UnionToTuple<T> = UnionToIntersection< T extends never ? never : (t: T) => T > extends (_: never) => infer W ? [ ... UnionToTuple<Exclude<T, W >> , W] : []; type A = UnionToTuple<'1' | '2' | '3'> // ['1', '2', '3'] Syntax 混亂 |> 容易把 Type 跟 Code 混在⼀起 |> Type syntax 可讀 性極差 TS
  47. TypeScript 的缺點 |> 不成熟 |> Third party library issue |>

    沒有真正的 Tagged Union Type |> 沒有 Pattern Match |> 沒有 New Type |> 難以理解的 Type Error Message |> 缺陷 |> Unsoundness |> Syntax 混亂 |> Type Infer 很差
  48. // map :: (a -> b) -> Array a ->

    Array b type CurriedMapFn = <T, N>(fn: (e: T) => N) => (x: T[]) => N[] const curriedMap:CurriedMapFn = (fn) => (arr) => arr.map(fn) type MapFn = <T, N>(fn: (e: T) => N, x: T[]) => N[] const map:MapFn = (fn, arr) => arr.map(fn) const result2 = map((a) => a + 1, [1,2,3]) const result = curriedMap((a) => a + 1)([1,2,3]) // Type Error, a is unknown Curry function 的 type inference 很差 TS #45438
  49. type primitiveType = 'string' | 'number' | 'boolean' const a

    = (p: primitiveType) => { if (p === 'string') { p // type is 'string' } } const b = <T extends primitiveType>(p: T) => { if (p === 'string') { p // type is still primitiveType } } Union type 的 type guard 在泛型中無 效 TS #36772
  50. TypeScript 的缺點 |> 不成熟 |> Third party library issue |>

    沒有真正的 Tagged Union Type |> 沒有 Pattern Match |> 沒有 New Type |> 難以理解的 Type Error Message |> 缺陷 |> Unsoundness |> Syntax 混亂 |> Type Infer 很差 |> Error 不是 First Class
  51. Error 不是 First Class |> 無法定義 Throw Error Type |>

    Promise 無法定義 Rejection Type
  52. let a: Promise<{ name: string, age: number }> a.then(r =>

    r.name) .catch(error => {}) Promise 無法定義 rejection type TS #39680
  53. TypeScript 的缺點 | > 不成熟 | > Third party library

    issue | > 沒有真正的 Tagged Union Type | > 沒有 Pattern Match | > 沒有 New Type | > 難以理解的 Type Error Message | > 缺陷 | > Unsoundness | > Syntax 混亂 | > Type Infer 很差 | > Error 不是 First Class | > Compiler 效能差
  54. Compiler 效能差 |> TypeScript 的 Compiler 是⽤ TypeScript 寫的 (跑在

    Node.js) |> ⼤型專案 Type Check 會很慢
  55. Deno Blog

  56. Compiler 效能差 |> TypeScript 的 Compiler 是⽤ TypeScript 寫的 (跑在

    Node.js) |> ⼤型專案 Type Check 會很慢 |> 電腦不能太差 |> TypeScript 團隊不打算⽤ Rust 重寫 Compiler
  57. None
  58. Compiler 效能差 |> TypeScript 的 Compiler 是⽤ TypeScript 寫的 (跑在

    Node.js) |> ⼤型專案 Type Check 會很慢 |> 電腦不能太差 |> TypeScript 團隊不打算⽤ Rust 重寫 Compiler |> ⽬前⽤別的語⾔重寫的 TypeScript Transpiler |> swc |> esbuild |> SWC 的作者正在⽤ Rust 重寫 TS Type Checker
  59. Conclusion ?

  60. 該不該花時間學 TypeScript ?

  61. 採⽤ TypeScript 的 Library 越來越多 以職涯的⾓度來說,不⼀定要⽤但必須要會

  62. None
  63. 專案該不該採⽤ TypeScript ?

  64. 專案該不該採⽤ TypeScript ? | > 採⽤或導入任何技術 | > 明確知道該技術的優缺點 |

    > 相關替代技術的橫向對比 | > 確認團隊成員的接受度 | > 團隊是否有⾜夠熟悉該技術的⼈ | > 未來是否好招⼈ | > 專案是否有⾜夠的資源做技術的轉換 | > 建議 | > 團隊中⾄少有⼀名以上的開發者熟悉 TypeScript | > 知道 generic type 的使⽤⽅式 | > 知道如何操作 type | > 知道 TypeScript 的地雷 | > 有良好的開發流程 - Code Review
  65. ⽤ TypeScript 開發的⼀些建議

  66. /* Strict Type-Checking Options */ "strict": true, /* Enable all

    strict type-checking options. */ "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ "strictNullChecks": true, /* Enable strict null checks. */ "strictFunctionTypes": true, /* Enable strict checking of function types. */ "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ TSConfig TS |> ⾄少要開 strictNullChecks |> 開發 Library 建議 直接 strict
  67. 開發⼿法 TS |> 記得 TS 的 Type Check 很笨 |>

    盡可能保持 Function 是 Pure |> 盡可能使⽤ immutable 的⽅式操 作資料 |> 留意任何 mutable 的 操作 |> 可以考慮採⽤
 eslint-plugin- immutable type A = { name: string, account: number } let a: A = { name: 'jerry', account: 123456 } type B = { name: string, account: number | string } let b: B = a b.account = '123' a.account.toFixed(0) // Runtime error
  68. 開發⼿法 TS |> 記得 TS 的 Type Check 很笨 |>

    盡可能保持 Function 是 Pure |> 盡可能使⽤ immutable 的⽅式操 作資料 |> 留意任何 mutable 的 操作 |> 可以考慮採⽤
 eslint-plugin- immutable type A = { name: string, account: number } const a: A = { name: 'jerry', account: 123456 } type B = { name: string, account: number | string } const b: B = { ... a, account: '123' } a.account.toFixed(0) // Ok
  69. const getUserName = (user: { firstName: string, lastName: string, age:

    number, phone: string }): string => { return `${user.firstName} ${user.lastName}` } 不要把 Type 跟 Code 混在⼀起 TS
  70. type User = { firstName: string, lastName: string, age: number,

    phone: string } type GetUserName = (user: User) => string const getUserName: GetUserName = (user) => { return `${user.firstName} ${user.lastName}` } 獨立寫 Type TS
  71. type User = { firstName: string, lastName: string, age: number,

    phone: string } type GetUserName = (user: User) => string const getUserName: GetUserName = (user) => { return `${user.firstName} ${user.lastName}` } getUserName({ firstName: 'Jerry', lastName: 'Hong' }) // Type Error 確保 Type 正確性 TS
  72. type User = { firstName: string, lastName: string, age: number,

    phone: string } type GetUserName = (user: { firstName: string, lastName: string }) => string const getUserName: GetUserName = (user) => { return `${user.firstName} ${user.lastName}` } getUserName({ firstName: 'Jerry', lastName: 'Hong' }) // Ok 不要重複寫 Type TS
  73. type User = { firstName: string, lastName: string, age: number,

    phone: string } type GetUserName = (user: Pick<User, 'firstName' | 'lastName'>) => string const getUserName: GetUserName = (user) => { return `${user.firstName} ${user.lastName}` } getUserName({ firstName: 'Jerry', lastName: 'Hong' }) // Ok 不要重複寫 Type TS
  74. type UncapitalizeKeys<T extends object> = Uncapitalize<keyof T & string>; type

    UncapitalizeObjectKeys<T extends object> = { [key in UncapitalizeKeys<T>]: Capitalize<key> extends keyof T ? T[Capitalize<key>] : never; } type A = UncapitalizeObjectKeys<{ FooHa: 'abc', Bar: 'def'}> TS 善⽤ type utility |> 熟悉官⽅提供的 Utility Types |> 善⽤第三⽅的 
 Type Utilities |> type-fest |> utility-types |> ts-toolbelt
  75. import { CamelCasedPropertiesDeep } from 'type-fest'; type A = CamelCasedPropertiesDeep<{

    FooHa: 'abc', Bar: 'def' }> TS 善⽤ type utility |> 熟悉官⽅提供的 Utility Types |> 善⽤第三⽅的 
 Type Utilities |> type-fest |> utility-types |> ts-toolbelt
  76. type UserInput = { name: string account: string password: string

    } type CreateUser = (input: UserInput) => Promise<User> const createUser: CreateUser = (input) => { // ... } app.get('/create-user', async (req, res) => { const userInput = req.body as UserInput const user = await createUser(userInput) }) 善⽤ Validator TS |> 永遠記得 TS 不保證 type safe |> 永遠不要相信從外部 世界來的 value |> Library |> zod |> ts-json-validator
  77. import { z } from 'zod'; const UserInputSchema = z.object({

    name: z.string().min(3).max(50), account: z.string().email(), password: z.string().min(7).max(50) }) type UserInput = z.infer<typeof UserInputSchema> type CreateUser = (input: UserInput) => Promise<User> const createUser: CreateUser = (input) => { // ... } app.get('/create-user', async (req, res) => { const userInput = await UserInputSchema.safeParseAsync(req.body) if (userInput.success) { const user = await createUser(userInput.data) } }) 善⽤ Validator TS |> 永遠記得 TS 不保證 type safe |> 永遠不要相信從外部 世界來的 value |> Library |> zod |> ts-json-validator
  78. import { Either, isRight } from 'fp-ts/Either'; type CreateUser =

    (input: UserInput) => Promise<Either<Error, User >> const createUser: CreateUser = (input) => { // ... } app.get('/create-user', async (req, res) => { const userInput = await UserInputSchema.safeParseAsync(req.body) if (userInput.success) { const userE = await createUser(userInput.data) if (isRight(userE)) { userE.right // User } else { userE.left // Error } } }) 進階: 處理 Error Type TS
  79. 良好的開發流程 |> 專⼈負責 Type |> Type ⼀致性 |> 檢查是否有 any

    |> 檢查 mutable 操作 |> 檢查外部資料是否 有驗證 |> Code Review |> CI - type check
  80. TypeScript 的學習資源

  81. TypeScript 的學習資源 |> TypeScript for Functional Programmers |> The TypeScript

    HandBook |> Type Challenge
  82. End.

  83. Any Question?