← Курс/Типизированные функции#145 из 257+30 XP

Типизированные функции в TypeScript

Типы параметров и возвращаемого значения

// Явные аннотации параметров и возвращаемого типа
function add(a: number, b: number): number {
  return a + b
}

// Стрелочная функция
const multiply = (a: number, b: number): number => a * b

// TypeScript выводит тип сам, если не указан явно
const divide = (a: number, b: number) => a / b  // TypeScript выводит: number

Опциональные и default параметры

// Опциональный параметр — может быть undefined
function greet(name: string, greeting?: string): string {
  return `${greeting ?? 'Привет'}, ${name}!`
}

// Default параметры — значение по умолчанию
function createUser(name: string, role: string = 'viewer'): User {
  return { name, role }
}

greet('Алексей')           // 'Привет, Алексей!'
greet('Алексей', 'Здрасте') // 'Здрасте, Алексей!'

Rest параметры

function sum(...numbers: number[]): number {
  return numbers.reduce((acc, n) => acc + n, 0)
}

sum(1, 2, 3, 4, 5)  // 15

Перегрузки функций (Overloads)

TypeScript позволяет описать несколько сигнатур для одной функции:

// Объявления перегрузок (только сигнатуры):
function format(value: string): string
function format(value: number): string
function format(value: Date): string

// Реализация (внутренняя, с union типом):
function format(value: string | number | Date): string {
  if (typeof value === 'string') return value.toUpperCase()
  if (typeof value === 'number') return value.toFixed(2)
  return value.toLocaleDateString('ru-RU')
}

Function types

Типы функций описывают сигнатуру как переменную:

// type alias для функции
type Callback = (err: Error | null, result: string) => void
type Predicate<T> = (item: T) => boolean
type Transform<A, B> = (input: A) => B

// Функция как параметр
function processItems(
  items: string[],
  filter: Predicate<string>,
  transform: Transform<string, string>
): string[] {
  return items.filter(filter).map(transform)
}

Generics в функциях

Generics (обобщения) — функция работает с разными типами, сохраняя типобезопасность:

// T — параметр типа (type parameter)
function identity<T>(arg: T): T {
  return arg
}

identity<string>('hello')  // возвращает string
identity<number>(42)       // возвращает number
identity('hello')          // TypeScript выводит T = string автоматически

// Практичный пример:
function first<T>(arr: T[]): T | undefined {
  return arr[0]
}

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

const lengths = map(['hello', 'world', 'hi'], s => s.length)
// TypeScript знает: lengths: number[]

pipe и compose

Классические функции функционального программирования — идеально подходят для дженериков:

// pipe применяет функции слева направо
function pipe<T>(...fns: Array<(x: T) => T>): (x: T) => T {
  return (x) => fns.reduce((acc, fn) => fn(acc), x)
}

const process = pipe(
  (x: number) => x * 2,    // double
  (x: number) => x + 1,    // addOne
  (x: number) => x ** 2,   // square
)

process(3)  // (3*2+1)^2 = 49

Примеры

Утилиты функционального программирования с runtime проверкой типов: pipe, compose, memoize, curry

// Реализация функциональных утилит с runtime-валидацией.
// В TypeScript типы гарантировали бы корректность при компиляции.

// pipe применяет функции слева направо: pipe(f, g, h)(x) = h(g(f(x)))
function pipe(...fns) {
  if (fns.length === 0) return x => x  // identity
  if (!fns.every(f => typeof f === 'function')) {
    throw new TypeError('pipe принимает только функции')
  }
  return function(x) {
    return fns.reduce((acc, fn) => fn(acc), x)
  }
}

// compose применяет функции справа налево: compose(f, g, h)(x) = f(g(h(x)))
function compose(...fns) {
  return pipe(...fns.reverse())
}

// memoize кэширует результаты вызовов
function memoize(fn) {
  if (typeof fn !== 'function') {
    throw new TypeError('memoize принимает функцию')
  }
  const cache = new Map()
  return function(...args) {
    const key = JSON.stringify(args)
    if (cache.has(key)) {
      return cache.get(key)
    }
    const result = fn.apply(this, args)
    cache.set(key, result)
    return result
  }
}

// curry превращает f(a, b, c) в f(a)(b)(c)
function curry(fn) {
  if (typeof fn !== 'function') {
    throw new TypeError('curry принимает функцию')
  }
  const arity = fn.length
  return function curried(...args) {
    if (args.length >= arity) {
      return fn.apply(this, args)
    }
    return function(...moreArgs) {
      return curried.apply(this, args.concat(moreArgs))
    }
  }
}

// --- Демонстрация pipe ---
console.log('=== pipe ===')
const double   = x => x * 2
const addOne   = x => x + 1
const square   = x => x ** 2
const negate   = x => -x

const transform = pipe(double, addOne, square)
console.log(transform(3))   // (3*2+1)^2 = 49
console.log(transform(4))   // (4*2+1)^2 = 81

const stringPipeline = pipe(
  s => s.trim(),
  s => s.toLowerCase(),
  s => s.replace(/\s+/g, '-'),
)
console.log(stringPipeline('  Hello World  '))  // 'hello-world'

// --- Демонстрация compose ---
console.log('\n=== compose ===')
const processNum = compose(negate, square, addOne, double)
console.log(processNum(3))  // -(((3*2)+1)^2) = -49

// --- Демонстрация memoize ---
console.log('\n=== memoize ===')
let callCount = 0
const expensiveFn = memoize(n => {
  callCount++
  return n * n
})

console.log(expensiveFn(5))  // 25 (вычисляет)
console.log(expensiveFn(5))  // 25 (из кэша)
console.log(expensiveFn(6))  // 36 (вычисляет)
console.log(`Вызовов функции: ${callCount}`)  // 2

// --- Демонстрация curry ---
console.log('\n=== curry ===')
const add = curry((a, b) => a + b)
const add5 = add(5)
console.log(add5(3))   // 8
console.log(add5(10))  // 15
console.log(add(2)(3)) // 5

const multiply = curry((a, b, c) => a * b * c)
console.log(multiply(2)(3)(4))  // 24
console.log(multiply(2, 3)(4))  // 24
console.log(multiply(2)(3, 4))  // 24