Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

Functional Programming 跟 Type 有什麼關係?

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Type in Functional Programming |> Type 就像是集合 (Set) |> Type 幫助我們定義 Function

Slide 7

Slide 7 text

Function 是什麼? 1 2 3 A B C X Y f(X) Pure f : X → Y

Slide 8

Slide 8 text

const add1 = x => 1 + x add1(2) // 3 ⽤ JS 定義⼀個 Add1 function JS add1('abc') // '1abc' type Add1 = (x: number) => number

Slide 9

Slide 9 text

add1 原本的意義 1 2 3 Number Add1(x)

Slide 10

Slide 10 text

add1 沒有 type 狀態下的意義不明 1 '2' 2 '12' ? ? Add1(x)

Slide 11

Slide 11 text

const add1 = x => 1 + x add1(2) // 3 定義不明 add1('abc') // '1abc' type Add1 = (x: number) => number JS

Slide 12

Slide 12 text

Type 幫助我們定 義 function TS const add1: Add1 = x => 1 + x add1(2) // 3 add1('abc') // Type Error type Add1 = (x: number) => number

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

就⼀般開發的⾓度 Type 帶來了哪些優勢 ?

Slide 15

Slide 15 text

Type 帶來的優勢 |> 型別檢查 (type check) 可以在 編譯時期 (compile time) 檢測出很多 潛在的 bugs |> 型別 (type) 能提供最新的⽂件 (up-to-date document) |> 型別 (type) 可以驅動開發 (Type-Driven Development)

Slide 16

Slide 16 text

Type 的優勢 !== TypeScript 的優勢

Slide 17

Slide 17 text

回顧 TypeScript 的發展歷史

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Typescript 的優缺點

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

採⽤ TypeScript 的 Library |> redux |> graphql-js |> apollo-client |> rxjs |> xstate

Slide 22

Slide 22 text

Github 2020 - octoverse

Slide 23

Slide 23 text

TypeScript 的優點 |> 許多 JS Library 採⽤ TypeScript |> Editor ⽀援良好

Slide 24

Slide 24 text

VS Code |> ⽬前最多⼈使⽤的 Editor |> 原⽣⽀援 TypeScript |> Go to definition |> Auto-complete |> Refactoring |> Renaming |> ...

Slide 25

Slide 25 text

TypeScript 的優點 |> 許多 JS Library 採⽤ TypeScript |> Editor ⽀援良好 |> 前後端可共⽤, 且⽣態⽀援好

Slide 26

Slide 26 text

前後端可共⽤, 且⽣態⽀援好 |> 前後端可共⽤ Type 甚⾄ Validation |> Library 及 Tools |> prisma |> typeorm |> apollo-server |> graphql-code-generator |> zod |> ts-json-validator

Slide 27

Slide 27 text

TypeScript 的優點 |> 許多 JS Library 採⽤ TypeScript |> Editor ⽀援良好 |> 前後端可共⽤, 且⽣態⽀援好 |> Type system 圖靈完備 (Turing Completeness)

Slide 28

Slide 28 text

Type system 圖靈完備 (Turing Completeness) | > 可以⽤ Type System 做任何運算 | > 可以做 Type Level Programming | > 定義⾃然數 | > 可以做到⼀些 Dependent type 的效果 | > Type check is undecidable | > Implement Collatz | > The Undecidability of the Generalized Collatz Problem

Slide 29

Slide 29 text

TypeScript 的優點 |> 許多 JS Library 採⽤ TypeScript |> Editor ⽀援良好 |> 前後端可共⽤, 且⽣態⽀援好 |> Type system 圖靈完備 (Turing Completeness) |> 在非嚴格模式及可⽤ Any 的狀況下, 對 JS 的開發者來說相對好上⼿

Slide 30

Slide 30 text

TypeScript 的缺點 |> 不成熟 |> Third Party Library Issue

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

TypeScript 的缺點 |> 不成熟 |> Third Party Library Issue |> 沒有真正的 Tagged Union Type |> 沒有 Pattern Match

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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 } }

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

TypeScript 的缺點 |> 不成熟 |> Third Party Library Issue |> 沒有真正的 Tagged Union Type |> 沒有 Pattern Match |> 沒有 New Type

Slide 38

Slide 38 text

type email = string type FindUser = (email: email) => User const findUser: FindUser = (email) => {} findUser('www') // no error 沒有 New Type |> 只有 type alias TS

Slide 39

Slide 39 text

沒有 New Type |> 只有 type alias |> 本質上是無法定義 ⼀個無限的集合 (如 email) String Email #33290

Slide 40

Slide 40 text

TypeScript 的缺點 |> 不成熟 |> Third Party Library Issue |> 沒有真正的 Tagged Union Type |> 沒有 Pattern Match |> 沒有 New Type |> 難以理解的 Type Error Message

Slide 41

Slide 41 text

type A = string | number function f(a: A) {} f(false) 難以理解的 Type Error Message TS

Slide 42

Slide 42 text

TypeScript 的缺點 |> 不成熟 |> Third party library issue |> 沒有真正的 Tagged Union Type |> 沒有 Pattern Match |> 沒有 New Type |> 難以理解的 Type Error Message |> 缺陷 |> Unsoundness

Slide 43

Slide 43 text

let x: number | null = 12 function nullX() { x = null } nullX() x.toFixed(1) // no error Unsoundness TS ⽬前還有 72 個 open unsound issue 在 github

Slide 44

Slide 44 text

TypeScript 的缺點 |> 不成熟 |> Third Party Library Issue |> 沒有真正的 Tagged Union Type |> 沒有 Pattern Match |> 沒有 New Type |> 難以理解的 Type Error Message |> 缺陷 |> Unsoundness |> Syntax 混亂

Slide 45

Slide 45 text

export function createMachine< TContext, TEvent extends EventObject = AnyEventObject, TTypestate extends Typestate = { value: any; context: TContext } >( config: MachineConfig, options?: Partial> ): StateMachine { return new StateNode( config, options ) as StateMachine; } Syntax 混亂 |> 容易把 Type 跟 Code 混在⼀起 TS

Slide 46

Slide 46 text

type Diff = T extends U ? never : T; // Remove types from T that are assignable to U type Filter = 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 extends never ? never : (arg: U) => never ) extends (arg: infer I) => void ? I : never; type UnionToTuple = UnionToIntersection< T extends never ? never : (t: T) => T > extends (_: never) => infer W ? [ ... UnionToTuple> , W] : []; type A = UnionToTuple<'1' | '2' | '3'> // ['1', '2', '3'] Syntax 混亂 |> 容易把 Type 跟 Code 混在⼀起 |> Type syntax 可讀 性極差 TS

Slide 47

Slide 47 text

TypeScript 的缺點 |> 不成熟 |> Third party library issue |> 沒有真正的 Tagged Union Type |> 沒有 Pattern Match |> 沒有 New Type |> 難以理解的 Type Error Message |> 缺陷 |> Unsoundness |> Syntax 混亂 |> Type Infer 很差

Slide 48

Slide 48 text

// map :: (a -> b) -> Array a -> Array b type CurriedMapFn = (fn: (e: T) => N) => (x: T[]) => N[] const curriedMap:CurriedMapFn = (fn) => (arr) => arr.map(fn) type MapFn = (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

Slide 49

Slide 49 text

type primitiveType = 'string' | 'number' | 'boolean' const a = (p: primitiveType) => { if (p === 'string') { p // type is 'string' } } const b = (p: T) => { if (p === 'string') { p // type is still primitiveType } } Union type 的 type guard 在泛型中無 效 TS #36772

Slide 50

Slide 50 text

TypeScript 的缺點 |> 不成熟 |> Third party library issue |> 沒有真正的 Tagged Union Type |> 沒有 Pattern Match |> 沒有 New Type |> 難以理解的 Type Error Message |> 缺陷 |> Unsoundness |> Syntax 混亂 |> Type Infer 很差 |> Error 不是 First Class

Slide 51

Slide 51 text

Error 不是 First Class |> 無法定義 Throw Error Type |> Promise 無法定義 Rejection Type

Slide 52

Slide 52 text

let a: Promise<{ name: string, age: number }> a.then(r => r.name) .catch(error => {}) Promise 無法定義 rejection type TS #39680

Slide 53

Slide 53 text

TypeScript 的缺點 | > 不成熟 | > Third party library issue | > 沒有真正的 Tagged Union Type | > 沒有 Pattern Match | > 沒有 New Type | > 難以理解的 Type Error Message | > 缺陷 | > Unsoundness | > Syntax 混亂 | > Type Infer 很差 | > Error 不是 First Class | > Compiler 效能差

Slide 54

Slide 54 text

Compiler 效能差 |> TypeScript 的 Compiler 是⽤ TypeScript 寫的 (跑在 Node.js) |> ⼤型專案 Type Check 會很慢

Slide 55

Slide 55 text

Deno Blog

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

No content

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

Conclusion ?

Slide 60

Slide 60 text

該不該花時間學 TypeScript ?

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

專案該不該採⽤ TypeScript ?

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

⽤ TypeScript 開發的⼀些建議

Slide 66

Slide 66 text

/* 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

Slide 67

Slide 67 text

開發⼿法 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

Slide 68

Slide 68 text

開發⼿法 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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

type UserInput = { name: string account: string password: string } type CreateUser = (input: UserInput) => Promise 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

Slide 77

Slide 77 text

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 type CreateUser = (input: UserInput) => Promise 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

Slide 78

Slide 78 text

import { Either, isRight } from 'fp-ts/Either'; type CreateUser = (input: UserInput) => Promise> 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

Slide 79

Slide 79 text

良好的開發流程 |> 專⼈負責 Type |> Type ⼀致性 |> 檢查是否有 any |> 檢查 mutable 操作 |> 檢查外部資料是否 有驗證 |> Code Review |> CI - type check

Slide 80

Slide 80 text

TypeScript 的學習資源

Slide 81

Slide 81 text

TypeScript 的學習資源 |> TypeScript for Functional Programmers |> The TypeScript HandBook |> Type Challenge

Slide 82

Slide 82 text

End.

Slide 83

Slide 83 text

Any Question?