타입스크립트 기초 - 13

Stulta Amiko·2022년 7월 7일
0

타입스크립트 기초

목록 보기
13/24
post-thumbnail

제네릭 함수

제네릭 타입은 인터페이스/클래스/타입별칭에 사용할수 있다.
제네릭은 앞에서 봤듯이 <>와 같은 꺾쇠 괄호를 이용해서 표현한다.

function t1<T>(a: T): void{}
const t2 = <T>(a: T):void =>{}
type t3<T> = (T) => void

위와같은 방식으로 표현할 수 있다.

아이덴티티 함수

맵함수의 가장 단순한 형태는 가공없이 그대로 반환하는 것이다.
즉 입력과 출력이 같은 형태인데 이를 함수형 프로그래밍 언어에서는 아이덴티티함수 라고 부른다.

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

const numIdentity: IdentityFunc<number> = (x: number): number => x

IdentityFunc<T> 의 경우 함수를 선언할때 포괄적으로 사용이 가능하다.

고차함수와 커리

함수의 매개변수의 갯수를 애리티라고 한다.

고차함수

고차함수는 어떤함수가 또다른 함수를 반환할때 그 함수를 고차함수라고 한다.
단순히 값을 반환하는 함수를 1차함수
1차 함수를 반환하면 2차 고차 함수
2차 고차 함수를 반환하면 3차 고차 함수라고 부르는 식이다.

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

const inc: FirstOrderFunc<number,number> = (x: number) => x+1

console.log(inc(1))

위함수는 1차 고차함수라고 볼 수 있을것이다.

type FirstOrderFunc<T,R> = (T) => R
type SecondOrderFunc<T,R> = (T) => FirstOrderFunc<T,R>

const add: SecondOrderFunc<number,number> = 
    (x: number): FirstOrderFunc<number, number> =>
    (y: number): number => x+y

console.log(add(1)(2))

위는 2차 고차함수이다 함수호출연산자를 두번 역속해서 사용했다.
함수형 프로그래밍언어에서는 이를 보고 커리라고 한다.

type FirstOrderFunc<T,R> = (T) => R
type SecondOrderFunc<T,R> = (T) => FirstOrderFunc<T,R>
type ThirdOrderFunc<T,R> = (T) => SecondOrderFunc<T,R>

const add: ThirdOrderFunc<number,number> = 
    (x: number): SecondOrderFunc<number,number> =>
    (y: number): FirstOrderFunc<number,number> =>
    (z: number): number => x+y+z


console.log(add(1)(2)(3))

3차 고차함수의 경우는 위와같은 모양으로 생겼다.

import { FirstOrderFunc,SecondOrderFunc } from "./type";
import {add} from './func'

const add1: SecondOrderFunc<number,number> = add(1)
console.log(add1(2),
add(1)(2))

책에나와있는 부분적용함수를 구현한것인데
작동이 안된다. 오류는 다음과같다.

Type 'FirstOrderFunc<number, number>' is not assignable to type 'SecondOrderFunc<number, number>'.
Type 'number' is not assignable to type 'FirstOrderFunc<number, number>'.

할당할 수 없다면서 이런 오류가 뜬다.

import { FirstOrderFunc,SecondOrderFunc,ThirdOrderFunc } from "./type";
import {add,add3} from './func'

const add2: SecondOrderFunc<number,number> = add3(1)
const add1: FirstOrderFunc<number,number> = add2(2)
console.log(add1(3),
add2(2)(3),
add3(1)(2)(3))

하지만 이런식으로 코드를 짜서 실행하면 문제가 없이 잘 작동된다.
다른예제를 하면서 보니 문제점을 파악했다.
책에서 타입을 지정할때 잘못지정한것이다.

import { FirstOrderFunc,SecondOrderFunc,ThirdOrderFunc } from "./type";
import {add,add3} from './func'

const add1: FirstOrderFunc<number,number> = add(1)

console.log(add1(2),add(1)(2))

이런식으로 지정을 하니 문제없이 잘 작동한다.

클로저

고차함수의 몸통에서 선언되는 변수들은 클로저라고 하는 유효범위를 가진다.
클로저는 지속되는 유효범위를 의미한다.

함수의 내부범위가 있는데 내부범위에서 의미를 알 수 없는 변수를 자유변수라고 부른다.

클로저는 메모리가 해제되지 않고 프로그램이 끝날때 까지 지속될 수도 있다.

const makeNames = (): () => string =>{
    const names = ['jack','jane','smith']
    let index = 0
    return (): string => {
        if(index == names.length)
            index = 0
        return names[index++]
    }
}

const makeName: () => string = makeNames()
console.log(
    [1,2,3,4,5,6].map(n=>makeName())
)

위와같이 이름을 출력하는 함수가 있다.

makeNames 함수내부에는 names와 index라는 자유변수가 있다.
index가 names.length값과 같아지면 다시 0이 되므로 makeName 함수를 사용하면 makeNames 함수에 할당된 클로저는 해제되지 않는다.

함수조합

함수조합은 작은 기능을 구현한 함수를 여러 번 조합해 더 의미있는 함수를 만들어 내는 기법이다. 함수조합을 할 수 있는 언어들은 compose나 pipe라는 이름의 함수를 제공하거나 만들 수 있다.

함수들 모두 애리티가 1이라면 연결해서 사용할 수 있다.

export const f = <T>(x: T): string => `f(${x})`
export const g = <T>(x: T): string => `g(${x})`
export const h = <T>(x: T): string => `h(${x})`

위에 있는 함수 f,g,h는 모두 애리티가 1인 함수이다.

compose 함수

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

위에서 만든 f,g,h 함수는 그대로 쓰고 compose 함수를 만들어 준 다음에

import {f,g,h} from './func'
import { compose } from './compose'

const composedFGH = compose(f,g,h)
console.log(
        composedFGH('x')
)

다음과 같이 사용하면

실행결과
h(g(f(x)))

와 같이 compose함수가 어떤 역할을 하는 함수인지 알 수 있게된다.

import { compose } from './compose'


const inc = x => x+1
const composed = compose(inc,inc,inc)
console.log(
        composed(1)
)

위와같은 방식으로 사용하게되면

(((x+1)+1)+1)과 같은 형식이 되므로
매개변수로 1을 넣었기때문에 4를 얻을 수 있다.

pipe 함수

pipe함수는 compose 함수와 해석하는 순서가 반대이다.

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

위와같이 pipe 함수를 만들고

const piped = pipe(f,g,h)
console.log(
        piped('x')
)

다음과 같은 방식으로 pipe 함수를 이용하면

실행결과
h(g(f(x)))

가 나오게 되는데 이는 compose에서 봤던것과 다르게 정반대의 결과를 내놓는것을 알 수 있다.

부분 함수와 함수 조합

import { compose,pipe } from './compose'

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

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

console.log(add3(1))

위와같은 방식으로 만들 수 있다.
add3는 pipe 함수를 가지고 inc와 add(2)를 합쳐서 만든 함수이고
따라서 순차적으로 작용함에 따라 결과는 4가 나오게된다.

0개의 댓글