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());