08-1 함수형 프로그래밍이란?

함수형 프로그래밍은 프로그램이 상태의 변화 없이 데이터 처리를 수학적 함수 계산으로 취급하고자 하는 패러다임이다.

 

함수형 프로그래밍은 1. 순수 함수, 2. 함수 조합, 3. 모나드 조합으로 코드를 설계하고 구현하는 기법이고, 다음 3가지 수학 이론에 기반을 두고 있다.

  1. 람다 수학: 조합 논리와 카테고리 이론의 토대가 되는 논리 수학
  2. 조합 논리: 함수 조합의 이론적 배경
  3. 카테고리 이론: 모나드 조합과 고차 타입의 이론적 배경

 

명령형 프로그래밍 vs 함수형(선언형) 프로그래밍
  • 명령형 프로그래밍
const name = 'Jae'
console.log(name) // Jae

 

  • 함수형 프로그래밍
function callName(name) {
    return name
}

console.log(callName('Jae')) // Jae

 


08-2 제네릭 함수

타입스크립트의 함수는 매개변수와 반환값에 타입이 존재하므로, 함수 조합을 구현할 때는 제네릭 함수 구문을 사용해야만 한다.

 

타입스크립트의 제네릭 함수 구문

타입스크립트에서 제네릭 타입은 함수, 인터페이스, 클래스, 타입 별칭에 적용할 수 있고, <T>, <T, Q>로 표현한다.

제네릭 타입으로 함수를 정의하면 어떤 타입에도 대응할 수 있다.

// function 함수
function g1<T>(a: T): void {}
function g2<T, Q>(a: T, b: Q): void {}

// 화살표 함수
const g3 = <T>(a: T): void => {}
const g4 = <T, Q>(a: T, b: Q): void => {}

// 타입 별칭
type Type1Func<T> = (T) => void
type Type2Func<T, Q> = (T, Q) => void
type Type3Func<T, Q, R> = (T, Q) => R

 

함수의 역할

프로그래밍 언어로 수학의 함수를 구현할 때는 변수의 타입을 고려해야 한다.

타입스크립트 언어로 일대일 맵 함수를 만든다면 타입 T인 값을 이용해 타입 R 값을 만들어 주어야 한다.

type MapFunc<T, R> = (T) => R

 

아이덴티티 함수

아이덴티티 함수는 입력과 출력의 타입이 같은 함수이다.

type MapFunc<T, R> = (T) => R
type IdentityFunc<T> = MapFunc<T, T>

const numberIdentity: IdentityFunc<number> = (x: number): number => x
const stringIdentity: IdentityFunc<string> = (x: string): string => x
const objectIdentity: IdentityFunc<object> = (x: object): object => x
const arrayIdentity: IdentityFunc<any[]> = (x: any[]): any[] => x

 


08-3 고차 함수와 커리

함수에서 매개변수의 개수를 에리티(arity)라고 한다.

f()는 에리티가 0개인 함수, f(x, y)는 에리티가 2개인 함수이다.

 

함수형 프로그래밍에서 compose나 pipe라는 이름의 함수를 사용해 compose(h, g, f) 또는 pipe(f, g, h) 형태로 함수들을 조합해 새로운 함수를 만들 수 있다.

 

고차 함수란?

타입스크립트에서 함수는 변수에 담긴 함수 표현식이고, 함수의 반환값으로 함수를 사용할 수 있다.

고차함수는 어떤 함수가 또 다른 함수를 반환하는 함수를 의미한다.

export type FirstFunc<T, R> = (T) => R
export type SecondFunc<T, R> = (T) => FirstFunc<T, R>
export type ThirdFunc<T, R> = (T) => SecondFunc<T, R>

 

부분 적용 함수와 커리

부분 적용 함수 또는 부분 함수는 자신의 차수보다 함수 호출 연산자를 덜 사용하는 함수이다.

 

클로저

고차 함수의 몸통에서 선언되는 변수들은 클로저(closer)라는 유효 범위를 가진다.

function add(x: number): (y: number) => number {
  return function(y: number): number {
    // 클로저
    return x + y
  }
}

// 변수 x 메모리 유지
const add1 = add(1)
console.log(add1) // [Function (anonymous)]

// result에 3을 저장 후 변수 x 메모리 해제
const result = add1(2)
console.log(result) // 3

 


08-4 함수 조합

함수 조합은 작은 기능을 구현한 함수를 여러 번 조합해 더 의미 있는 함수를 만들어 내는 프로그램 설계 기법이다.

함수 조합을 할 수 있는 언어들은 composer 혹은 pipe 라는 이름의 함수를 제공하거나 만들 수 있다.

 

composer, pipe 함수는 자바스크립트에 존재하는 함수가 아닌 함수 패턴으로 직접 구현해야 한다.

 

compose 함수

  1. compose 함수는 가변 인수 스타일로 함수들의 배열을 입력받는다. (...functions)
  2. compose 함수는 인자로 받은 함수 리스트를 오른쪽에서 왼쪽으로 실행하기 때문에 reverse() 한다.
export const compose = <T, R>(...functions: readonly Function[]): Function => (x: any): (T: any) => R => {
  const deepCopiedFunctions = [...functions]
  return deepCopiedFunctions.reverse().reduce((value, func) => func(value), x)
}

const inc = (x: number): number => x + 1
const composed = compose(inc, inc, inc)

console.log(composed(1)) // 4

 

pipe 함수

  1. pipe 함수는 가변 인수 스타일로 함수들의 배열을 입력받는다. (...functions)
  2. pipe 함수는 인자로 받은 함수 리스트를 왼쪽에서 오른쪽으로 실행하기 때문에 reverse() 코드가 필요 없다.

 

pipe 함수는 compose와 매개변수들을 해석하는 순서다 반대이므로, functions을 reverse() 하는 코드가 없다.

위의 compose 함수와 결과가 동일하다.

export const pipe = <T>(...functions: Function[]): Function => (x: T): T => {
  return functions.reduce((value, func) => func(value), x)
}

const inc = (x: number): number => x + 1
const composed = pipe(inc, inc, inc)

console.log(composed(1)) // 4

 

pipe와 compose 함수 분석

  1. compose, pipe 함수는 가변 인수 방식으로 동작하므로, 매개변수를 전개 연산자로 설정한다.
export const pipe = (...functions)

 

    2. 각 함수의 시그니처가 모두 다르면 제네릭 타입을 적용하기 힘들므로,  functions의 타입은 함수들의 배열인 Function[]으로 설정한다.

export const pipe = (...functions: Function[])

 

    3. pipe 함수로 조합된 결과 함수는 애리티가 1이므로, 매개변수 x를 입력받는 함수를 작성한다.

export const pipe = <T>(...functions: Function[]): Function => (x: T) => T

 

    4. pipe 함수는 reduce() 메서드를 사용해 배열의 각 요소에 대해 주어진 함수를 실행하고, 하나의 결괏값을 반환한다.

export const pipe = <T>(...functions: Function[]): Function => (x: T): T => {
  return functions.reduce((value, func) => func(value), x)
}

 

compose 함수는 pipe 함수와 매개변수 방향이 반대이다.

즉, pipe(f, g, h) === compose(h, g, f)

 

    5. compose 함수는 매개변수를 전개 연산자로 전개한 다음, 그 내용을 깊은 복사 한다.

export const compose = <T>(...functions: readonly Function[]): Function => (x: T): T => {
  const deepCopiedFunctions = [...functions]
}

 

    6. compose 함수는 매개변수 방향이 반대이기 때문에 reverse()를 사용해 결괏값을 반환한다.

export const compose = <T>(...functions: readonly Function[]): Function => (x: T): T => {
  const deepCopiedFunctions = [...functions]
  return deepCopiedFunctions.reverse().reduce((value, func) => func(value), x)
}

 

부분 함수와 함수 조합

고차 함수의 부분 함수는 함수 조합에서 사용될 수 있다.

 

add 함수는 2차 고차 함수이다.

inc 함수는 add의 부분 함수이다.

reault 함수는 pipe 함수를 갖고, inc, add 함수를 조합해서 만든 함수이다.

export const pipe = <T>(...functions: Function[]): Function => (x: T): T => {
  return functions.reduce((value, func) => func(value), x)
}

const add = (x: number) => (y: number) => x + y
const inc = add(1)

const result = pipe(inc, add(2))

console.log(result(3)) // 6

 

포인트가 없는 함수

map 함수는 함수 조합을 고려해 설계한 것으로, map(f) 형태의 부분 함수를 만들면  compose나 pipe에 사용할 수 있다.

이처럼 함수 조합을 고려해 설계한 함수를 '포인트가 없는 함수' 라고 한다.

export const pipe = <T>(...functions: Function[]): Function => (x: T): T => {
  return functions.reduce((value, func) => func(value), x)
}

export const map = (f: any) => (a: any) => a.map(f)

const square = (value: number) => value * value
const squaredMap = map(square)
const fourSquare = pipe(squaredMap, squaredMap)

console.log(fourSquare([2, 3])) // [16, 81]

 

함수 조합은 복잡하지 않은 함수들은 compose 또는 pipe로 조합해 복잡한 내용을 쉽게 만들 수 있다.

 

 

 

참고자료

Do it! 타입스크립트 프로그래밍

+ Recent posts