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)
filter(['a', 'b'], _ => _ !== 'b')
When Are Generics Bound?
type Filter<T> = {
(array: T[], f: (item: T) => boolean): T[]
}
const filter: Filter = (array, f) => {
return array.filter(f)
}
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)
map<number>(xs, x => !!x)
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
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] }
function mapNode<T extends TreeNode> (
node: T,
f: (value:string) => string
): T {
return {
...node,
value: f(node.value)
}
}
const a1 = mapNode(a, _ => _.toUpperCase())
const b1 = mapNode(b, _ => _.toUpperCase())
const c1 = mapNode(c, _ => _.toUpperCase())
Generic Type Defaults
type Hoge<T> = {
(n: T): T
}
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
}
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)