📖 TypeScript Mastery - TypeScript với Functional Programming
65 phút

TypeScript với Functional Programming

Higher-Order Functions

Function Types

type Mapper<T, U> = (value: T, index: number, array: T[]) => U;
type Predicate<T> = (value: T, index: number, array: T[]) => boolean;
type Reducer<T, U> = (accumulator: U, current: T, index: number, array: T[]) => U;

function map<T, U>(array: T[], mapper: Mapper<T, U>): U[] {
  return array.map(mapper);
}

function filter<T>(array: T[], predicate: Predicate<T>): T[] {
  return array.filter(predicate);
}

function reduce<T, U>(array: T[], reducer: Reducer<T, U>, initial: U): U {
  return array.reduce(reducer, initial);
}

Currying và Partial Application

// Curried function
type Curried<T, U, R> = T extends []
  ? R
  : (arg: T) => U extends [] ? R : Curried<U, R>;

function curry<T extends any[], U, R>(
  fn: (...args: [...T, ...U]) => R
): Curried<T, U, R> {
  return fn as any; // Simplified implementation
}

// Practical example
const add = (a: number, b: number, c: number): number => a + b + c;
const curriedAdd = curry(add);

const add5 = curriedAdd(5);
const add5And10 = add5(10);
const result = add5And10(3); // 18

Immutable Data Structures

Readonly Arrays và Objects

const immutableArray: readonly number[] = [1, 2, 3];
// immutableArray.push(4); // Error!

interface ImmutablePoint {
  readonly x: number;
  readonly y: number;
}

const point: ImmutablePoint = { x: 10, y: 20 };
// point.x = 15; // Error!

Immutable Updates

function updateObject<T extends object, K extends keyof T>(
  obj: T,
  updates: { [P in K]?: T[P] }
): T {
  return { ...obj, ...updates };
}

const user = { name: 'John', age: 30, active: true };
const updatedUser = updateObject(user, { age: 31 });

// With arrays
function addItem<T>(array: readonly T[], item: T): T[] {
  return [...array, item];
}

function removeItem<T>(array: readonly T[], index: number): T[] {
  return [...array.slice(0, index), ...array.slice(index + 1)];
}

Monads và Functional Patterns

Option Type (Maybe)

type Option<T> = Some<T> | None;

interface Some<T> {
  readonly _tag: 'Some';
  readonly value: T;
}

interface None {
  readonly _tag: 'None';
}

const some = <T>(value: T): Some<T> => ({ _tag: 'Some', value });
const none: None = { _tag: 'None' };

function mapOption<T, U>(option: Option<T>, fn: (value: T) => U): Option<U> {
  return option._tag === 'Some' ? some(fn(option.value)) : none;
}

function flatMapOption<T, U>(option: Option<T>, fn: (value: T) => Option<U>): Option<U> {
  return option._tag === 'Some' ? fn(option.value) : none;
}

// Usage
function findUser(id: number): Option<{ name: string; email: string }> {
  return id > 0 ? some({ name: 'John', email: 'john@example.com' }) : none;
}

const userEmail = flatMapOption(
  findUser(1),
  user => some(user.email.toUpperCase())
);

Result Type (Either)

type Result<T, E = Error> = Success<T> | Failure<E>;

interface Success<T> {
  readonly _tag: 'Success';
  readonly value: T;
}

interface Failure<E> {
  readonly _tag: 'Failure';
  readonly error: E;
}

const success = <T>(value: T): Success<T> => ({ _tag: 'Success', value });
const failure = <E>(error: E): Failure<E> => ({ _tag: 'Failure', error });

function mapResult<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E> {
  return result._tag === 'Success' ? success(fn(result.value)) : result;
}

function flatMapResult<T, U, E>(
  result: Result<T, E>, 
  fn: (value: T) => Result<U, E>
): Result<U, E> {
  return result._tag === 'Success' ? fn(result.value) : result;
}

Function Composition

Pipe Function

function pipe<T>(value: T): T;
function pipe<T, A>(value: T, fn1: (value: T) => A): A;
function pipe<T, A, B>(value: T, fn1: (value: T) => A, fn2: (value: A) => B): B;
function pipe<T, A, B, C>(
  value: T, 
  fn1: (value: T) => A, 
  fn2: (value: A) => B,
  fn3: (value: B) => C
): C;
function pipe(value: any, ...fns: Function[]): any {
  return fns.reduce((acc, fn) => fn(acc), value);
}

// Usage
const result = pipe(
  5,
  (x: number) => x * 2,
  (x: number) => x + 1,
  (x: number) => `Result: ${x}`
); // "Result: 11"

Compose Function

function compose<T, U, V>(f: (x: U) => V, g: (x: T) => U): (x: T) => V {
  return (x: T) => f(g(x));
}

function composeMany<T>(...fns: Function[]): (x: T) => any {
  return (x: T) => fns.reduceRight((acc, fn) => fn(acc), x);
}

// Usage
const add1 = (x: number) => x + 1;
const multiply2 = (x: number) => x * 2;
const toString = (x: number) => x.toString();

const transform = composeMany(toString, multiply2, add1);
const output = transform(5); // "12"

Lenses và Immutable Updates

Simple Lens Implementation

interface Lens<T, U> {
  get: (obj: T) => U;
  set: (obj: T, value: U) => T;
}

function lens<T, U>(getter: (obj: T) => U, setter: (obj: T, value: U) => T): Lens<T, U> {
  return { get: getter, set: setter };
}

function view<T, U>(lens: Lens<T, U>, obj: T): U {
  return lens.get(obj);
}

function set<T, U>(lens: Lens<T, U>, obj: T, value: U): T {
  return lens.set(obj, value);
}

function over<T, U>(lens: Lens<T, U>, obj: T, fn: (value: U) => U): T {
  return lens.set(obj, fn(lens.get(obj)));
}

// Usage
interface User {
  name: string;
  address: {
    street: string;
    city: string;
  };
}

const addressLens = lens(
  (user: User) => user.address,
  (user: User, address) => ({ ...user, address })
);

const cityLens = lens(
  (address: User['address']) => address.city,
  (address: User['address'], city) => ({ ...address, city })
);

const userCityLens: Lens<User, string> = {
  get: (user) => cityLens.get(addressLens.get(user)),
  set: (user, city) => addressLens.set(user, cityLens.set(addressLens.get(user), city))
};

const user: User = {
  name: 'John',
  address: { street: '123 Main St', city: 'Hanoi' }
};

const updatedUser = over(userCityLens, user, city => city.toUpperCase());

📝 Bài tập (1)

  1. Xây dựng thư viện FP với TypeScript types

Bài học "TypeScript với Functional Programming" - Khóa học "TypeScript Mastery"