Programming TypeScript ch4 (2/2)

TypeScript勉強メモ

出典: 


Polymorphism

type Filter = {
  <T>(array: T[], f: (item: T) => boolean): T[]
}

const filter: Filter = (array, f) => {
  return array.filter(f)
}


filter([1, 2, 3], _ => _ > 2)      // T is bound to number
filter(['a', 'b'], _ => _ !== 'b') // T is bound to string
  • 引数で型引数が推論される

When Are Generics Bound?

type Filter<T> = {
  (array: T[], f: (item: T) => boolean): T[]
}

// Error: Generic type 'Filter' requires 1 type argument(s).
// Error: Parameter 'array' implicitly has an 'any' type.
// Error: Parameter 'f' implicitly has an 'any' type.
const filter: Filter = (array, f) => {
  return array.filter(f)
}

// Error: Generic type 'Filter' requires 1 type argument(s).
type OtherFilter = Filter
  • Filter<T>とすると、明示的に型引数を渡す必要がある
type Filter<T> = {
  (array: T[], f: (item: T) => boolean): T[]
}

const filter: Filter<number> = (array, f) => {
  return array.filter(f)
}

type NumberFilter = Filter<number>

const numberFilter: NumberFilter = (array, f) => {
  return array.filter(f)
}

Where Can You Declare Generics?

type Filter1 = {
  <T>(array: T[], f: (item: T) => boolean): T[]
}

type Filter2<T> = {
  (array: T[], f: (item: T) => boolean): T[]
}


type Filter3 = <T>(array: T[], f: (item: T) => boolean) => T[]

type Filter4<T> = (array: T[], f: (item: T) => boolean) => T[]

function filter<T>(array: T[], f: (item: T) => boolean): T[] {
  return array.filter(f)
}
  • 1と3とは同じ意味のショートハンド
  • 2と4とは同じ意味のショートハンド
  • 5は関数宣言

Column: filter and map in the Standard Library

filter(callbackfn: (value: T, index: number, array: T[]) => unknown, thisArg?: any): T[];

map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];

Generic Type Inference

const xs = [1, 2, 3]

function map<T, U>(array: T[], f: (item: T) => U) {
  return array.map(f)
}

map(xs, x => !!x)
map<number, boolean>(xs, x => !!x)

// Error: Expected 2 type arguments, but got 1.
// Error: Parameter 'x' implicitly has an 'any' type.
map<number>(xs, x => !!x)
  • 型引数の指定はall-or-nothing

Generic Type Aliases

const xs = [1, 2, 3]

type MyMap<T,U> = {
    (array:T[], f:(item:T)=>U): U[]
}

type MapNumberToBoolean = MyMap<number,boolean>

Bounded Polymorphism

  • 2分木
type TreeNode = {
  value: string
}

type LeafNode = TreeNode & {
  isLeaf: true
}

type InnerNode = TreeNode & {
  children: [TreeNode] | [TreeNode, TreeNode]
}

const a: TreeNode = { value: 'a' }
const b: LeafNode = { value: 'b', isLeaf: true }
const c: InnerNode = { value: 'c', children: [b] }
  • valueのmap関数
function mapNode<T extends TreeNode> (
  node: T,
  f: (value:string) => string
): T {
  return {
    ...node,
    value: f(node.value)
  }
}


const a1 = mapNode(a, _ => _.toUpperCase()) // TreeNode
const b1 = mapNode(b, _ => _.toUpperCase()) // LeafNode
const c1 = mapNode(c, _ => _.toUpperCase()) // InnerNode
  • T extends TreeNode が Bounded Polymorphism

    • 「少なくともTreeNode型」というupper boundをもうける

Generic Type Defaults

type Hoge<T> = {
  (n: T): T
}

// Error: Generic type 'Hoge' requires 1 type argument(s).
// Error: Parameter 'n' implicitly has an 'any' type.
const hoge: Hoge = n => {
  return n
}

const hogeNum: Hoge<number> = n => {
  return n
}

const hogeStr: Hoge<string> = str => {
  return str
}
  • デフォルト型引数を指定可能
type Hoge<T = number> = {
  (n: T): T
}

// OK
const hoge: Hoge = n => {
  return n
}

const hogeNum: Hoge<number> = n => {
  return n
}

const hogeStr: Hoge<string> = str => {
  return str
}

Type-Driven Development

  • 型シグネチャを先に決めてから値のプログラムを実装する

Exercises

function call<T extends unknown[], R>(
  f: (...args: T) => R,
  ...args: T
): R {
  return f(...args)
}

call(console.log.bind(console), 1, 2, 3)
  • 第二引数がstringの関数に対してのみ動作するようにする
function call<T extends [unknown?, string?, ...unknown[]], R>(
  f: (...args: T) => R,
  ...args: T
): R {
  return f(...args)
}

function withUnit(n: number, unit: string) {
  return `${n} ${unit}`
}

function add(a: number, b: number) {
  return a + b
}

call(withUnit, 1, 'px')
call(add, 1, 2) // Error: Argument of type '(a: number, b: number) => number' is not assignable to parameter of type '(args_0?: unknown, args_1?: string | undefined, ...args_2: unknown[]) => number'.