타입스크립트 프로그래밍 책을 보며 공부중입니다.

04-1 함수 선언문

자바스크립트에서 함수는 function 키워드로 만드는 함수(함수선언문)과 =>기호로 만드는 화살표 함수가 있다.
함수 선언문의 구조를 보자

function 함수 이름(매개변수1, 매개변수2[, ...]){
  함수 몸통
}

타입스크립트 함수 선언문은 자바스크립트 함수 선언문에서 매개변수와 함수 반환값(return type)에 타입 주석을 붙이는 다음 형태로 구성된다.

function 함수 이름(매개변수1: 타입1, 매개변수2: 타입2[, ...]) : 반환값 타입 {
  함수 몸통
}

다음 코드는 타입 스크립트 함수 선언문 예이다.

function add(a:number, b: number): number{
  return a+b;
}

매개변수와 반환값의 타입 주석 생략

변수 때와 마찬가지로 함수 선언문에서도 매개변수와 반환겂에 대한 타입 주석을 생략할 수 있다.
다만, 변수 때와는 달리 함수의 매개변수 타입과 반환 타입을 생략하는것은 바람직하지 않다. 타입이 생략되어 있으면 함수의 구현 의도를 알기 어려워 잘못 사용할 우려가 있다.

void 타입

값을 반환하지 않는 함수는 반환 타입이 void이다. void 타입은 함수 반환 타입으로만 사용 할 수 있다.

// void 타입 예
function printMe(name: string, age: number): void{
  consoe.log(`name : ${name}, age: ${age}`)
}

함수 시그니쳐

변수에 타입이 있듯이 함수 또한 타입이 있는데, 함수의 타입은 함수 시그니처(function signature) 라고 한다. 함수의 시그니처는 다음과 같은 형태로 표현한다.

(매개변수1 타입, 매개변수2 타입[, ...]) => 반환값 타입

다음 코드에 앞선 예에서 printMe함수의 시그니처를 이용한 예이다. printMe 함수는 string과 number 타입의 매개변수가 두 개 있고 반환 타입이 void 이다. 따라서 함수 시그니처는 (string, number) => void 이다.

// 함수 시그니처 사용 예
let printMe: (string, number) => void = function (name: string, age: number) : void {}

만약, 매개변수가 없으면 단순히 ()로 표현한다. () => void는 매개변수도 없고 반환값도 없는 함수 시그니처이다.

type 키워드로 타입 별칭 만들기

타입스크립트는 type이라는 키워드를 제공한다. type 키워드는 기존에 존재하는 타입을 단순히 이름만 바꿔서 사용할 수 있게 해준다. 이러한 기능을 타입 별칭(type alias) 이라고 한다.

type 새로운 타입 = 기존 타입

다음 코드에서 01행은 (string, number) => void 함수 시그니처를 stringNumberFunc이라는 이름으로 타입 별칭을 만든다. 이 별칭 덕분에 02행과 03행에서 선수 f와 g에 타입 주석을 간단하게 붙일 수 있다.

type stringNumberFunc = (string, number) => void
let f: stringNumberFunc = function(a: stirng, b: number): void{}
let g: stringNumberFunc = function(c: stirng, d: number): void{}

함수의 타입, 즉 함수 시그니처를 명시하면 다음 코드에서 보는 것처럼 매개변수의 개수나 타입, 반환 타입이 다른 함수를 선언하는 잘못을 방지할 수 있다.

type stringNumberFunc = (string, number) => void
let f: stringNumberFunc = function(a: stirng, b: number): void{}
let g: stringNumberFunc = function(c: stirng, d: number): void{}
let h : stringNumberFunc = function(): {}
h() // 에러

undefined 관련 주의 사항

03-1 절에서 undefined 타입을 설명하였다. undefined 타입은 타입스크립트의 타입 계층도에서 모든 타입 중 최하위 타입이다. 다음은 undefined 타입을 고려하지 않은 예이다.

interface INameable{
  name: string
}
function getName(o:INameable) {return o.name}
let n = getname(undefined) // 오류발생
console.log(n)

코드에서 04행의 getName은 INameable 타입의 매개변수를 요구하지만, 06행에서 INameable타입 객체가 아니라 undefined를 매개변수로 호출해도 구문 오류가 발생하지 않는다. 즉, undefined는 최하위 타입이므로 INameabel을 상속하는 자식 타입으로 간주한다.
하지만 코드를 실행해 보면 04행의 o.name 부분이 undefined.name이 되어 'Cannot read prorperty 'name' of undefined'라는 오류가 발생합니다. 이런 오류를 방지하려면 매개변수값이 undefined인지 판별하는 코드를 작성해야 합니다. 다음 코드에서 getName 함수의 몸통은 매개변수 o의 값이 undefined일 때를 고려한 예입니다.

//undefined.ts
interface INameable{
  name:string
}
function getName(o: INameable){
  return o != undefined ? o.name : 'unknown name'
}

let n = getName(undefined);
console.log(n); //unknown name
console.log(getName({name:'Jack'})); // Jack

만일, 인터페이스에 선택 속성이 있다면 코드는 다음 05행처럼 구현해야 합니다.

//optional.ts
interface IAgeable{
  age?:number
}
function getAge(o: IAgeable){
  return o!= undefined && o.age ? o.age : 0
}

console.log(getAge(undefined)); // 0
console.log(getAge(null)); // 0
console.log(getAge({age:32})); // 32

선택적 매개변수

03-2절에서 인터페이스의 선택 속성을 설명했다. 함수의 매개변수에도 다음처럼 이름뒤에 물음표를 붙일 수 있으며, 이를 선택적 매개변수(optional parameter)라고 합니다.

function fn(arg1: string, arg?: number): void {}

선택적 매개변수는 다음 코드에서 03행과 04행의 함수 호출을 모두 가능하게 하고 싶을 때 사용합니다.

//optional-arg.ts
function fn(arg1: string, arg?: number) {console.log(`arg: ${arg}`)}

fn('hello',1) // arg:1
fn('hello') // arg: undefined

선택적 매개변수가 있는 함수의 시그니처는 다음처럼 타입 뒤에 물음표를 붙입니다.

type OptionalArgFunc = (string, number?) => void

04-2 함수 표현식

함수는 객체다

자바스크립트는 함수형언어 '스킴(scheme)'과 프로토타입(prototype) 기반 객체지향 언어 '셀프(self)'를 모델로 만들어졌다. 따라서 자바스크립트는 객체지향 언어와 함수형 언어의 특징이 모두 있다. 타입스크립트 또한 자바스크립트의 이런 특징을 모두 포함한다. 자바스크립트에서 함수는 Function 클래스의 인스턴스(instance)이다. 다음 코드를 실행해 보면 3이 출력되는데, 이는 add가 함수로 동작한다는 의미이다.

let add = new Function('a','b', 'return a + b');
let result = add(1,2)
console.log(result) // 3

그런데 01행의 내용이 조금 특이하다. add가 함수라면 다음과 같은 구문이어야 하는데, 01행은 변수 선언문 형태로 add함수를 구현했기 때문이다.

function add(a,b) {return a + b}

사실 add 함수는 다음과 같은 형태로도 구현이 가능하다.

let add2 = function(a, b) {return a + b}
console.log(add2(1, 2)) // 3

이처럼 함수 선언문에서 함수 이름을 제외한 function(a, b) {return a + b}와 같은 코드를 함수 표현식(function expression) 이라고 한다. 함수 표현식은 함수형 언어의 핵심 기능이다. 이제 '일등 함수'라는 용어를 시작으로 함수 표현식이 무엇인지 살펴보자.

일등 함수

프로그래밍 언어가 일등함수 기능을 제공하면 '함수형 프로그래밍 언어' 라고 한다. 자바스크립트와 타입스크립트는 일등 함수 기능이 있으므로 함수형 프로그래밍 언어이다. 일등 함수란, 함수와 변수를 구분하지 않는다는 의미이다.

예를 들어, 다음 코드에서 01행의 f는 let 키워드가 앞에 있으므로 변수이다. f는 변수이므로 값을 저장할수 있다. 변수 f에는 a + b 형태의 함수표현식을 저장하였다. 하지만 f는 변수이므로 02행 처럼 a - b 형태의 함수 표현식도 저장이 가능하다.

first-class.ts

let f= function(a,b) {return a + b}
f = function(a,b) {return a - b}

심벌 f가 변수인지 함수인지 사실상 구분할 수 없다. 이것이 변수와 함수를 차별하지 않는다는 의미이다.

표현식

프로그래밍 언어에서 '표현식' 이라는 용어는 리터럴, 연산자, 변수, 함수 호출 등이 복합적으로 구성된 코드 형태를 의미한다. 예를 들어, 1 + 2는 1과 2라는 리터럴과 덧셈 연산자 +로 구성된 표현식이다. 표현식은 항상 컴파일러에 의해 계산법이 적용되어 어떤 값이 된다. 예를 들어, 표현식 1 + 2는 컴파일러에 의해 3이라는 값이 된다.

함수 표현식

앞에서 작성한 first-class.ts의 01행에서 변수 f에는 function(a, b) {return a + b}를 마치 값처럼 대입하는데, 이 function(a, b) {return a + b;} 부분을 함수 표현식 이라고 합니다.

계산법

컴파일러는 표현식을 만나면 계산법을 적용해 어떤 값을 만든다. 계산법에는 조급한 계산법과 느긋한(지연) 계산법 두 가지가 있다.
컴파일러가 1 + 2 라는 표현식을 만나면 조급한 계싼법을 적용해 3이라는 값을 마든다. 반면 컴파일러가 function(a, b) {return a + b}라는 함수 표현식을 만나면, 심벌 a와 b가 어떤 값인지 알수 없어 느긋한 계산법을 적용하여 계산을 보류한다.

함수 호출 연산자

어떤 변수가 함수 표현식을 담고 있다면, 변수 이름 뒤에 함수 호출 연산자 ()를 붙여서 호출할 수 있다. 여기서 '함수 호출'이란, 함수 표현식의 몸통 부분을 실행한다는 뜻이다. 만약, 함수가 매개변수를 명시할 수 있다.
다음 코드에서 01행의 functionExpression 변수는 funcion(a, b) {return a + b}라는 함수 표현식을 담고 있다. functionExpression 변수는 함수 표ㅕ현식을 담고 있으므로, 02행처럼 변수 이름 뒤에 함수 호출 연산자 (1, 2)를 붙여 functionExpression(1, 2)라는 함수 호출문을 만들 수 있다.

let functionExpression = function(a, b) {return a + b}
let value = functionExpression(1, 2) // 3

컴파일러는 함수 호출문을 만나면 지금까지 미뤘던 함수 표현식에 조급한 계산법을 적용해 함수 표현식을 값으로 바꿉니다. 즉, functionExpression(1, 2) 형태로 함수가 호출되면, 컴파일러는 functionExpression 변수에 저장된 함수 표현식을 끄집어 낸 뒤 조급한 계산법을 적용한다.

함수 표현식에 조급한 계산법을 적용한다는 의미는 함수 표현식의 몸통 부분을 실행한다는 의미이다. 앞 코드에서 함수 몸통은 return a + b인데, 매개변수 a와 b의 값이 1과 2로 확정되면 몸통은 return 1 + 2가 된다. 여기에 다시 조급한 계산법이 적용되어 return 3이 된다. 그리고 최종적으로 functionExpression(1, 2) 라는 표현식은 3이라는 값이 된다.

익명 함수

함수 표현식은 사실 대부분 언어에서 언급되는 익명 함수의 다른 표현이다. 자바스크립트에서는 가끔 다음과 같은 형태로 작성된 코드를 만난다. 앞에서 살펴본 함수 표현식 개념이 없는 상태에서 단순히 익명 함수를 '이름이 없는 함수'로만 이해한다면 이런 형태의 코드가 어떻게 동작하는지 원리를 쉽게 가늠하기 어렵다.

anonymous.ts

let value = (function(a,b) {return a +b;}) (1, 2) ///3

위 코드를 이해하려면 연산자 우선 순위 를 고려해 코드를 분해해야 한다. 일반적으로 연산자들이 우선순위가 다르면 (1 + 2) * 5처럼 소괄호를 사용해 우선순위를 변경한다. 마찬가지로 함수 호출 연산자는 연산자의 우선순위가 매우 높으므로 함수 표현식 부분을 소괄호로 묶어 컴파일러가 정상적으로 함수 표현식의 시작과 끝 부분을 알 수 있게 해야 한다.
아래 코드는 앞의 한줄짜리 코드를 쉽게 분석하고자 세 줄로 나누어 보았다.

let value =
(function (a, b) {return a+b})
(1,2)

컴파일러는 02행의 익명함수 부분에 게으른 계산법을 적용하여 그 상태로 놔두지만, 곧바로 03행의 함수 호출 연산자를 만나므로 02행의 함수 몸통에 조급한 계산법을 적용해 최종적으로 3이라는 값을 만들어 낸다. 그 다음 01행의 value 변수에 이 값을 대입한다.

const 키워드와 함수 표현식

함수 표현식을 담는 변수는 let보다는 const 키워드로 선언하는 것이 바람직하다. let 키워드는 변숫값이 변할 수 있으므로 다음처럼 코드를 작성하면 함수 f는 언젠가 다른 내용으로 바뀔 수 있다.

let f = () => {}

반면에 함수 표현식을 담는 변수를 const 키워드로 선언하면, 함수 내용이 이후에 절대로 바뀔 수 없다. 따라서 앞으로는 함수 표현식을 담는 변수는 const로 선언하자.

const f = () => {}

04-3 화살표 함수와 표현식 문

ESNext 자바스크립트와 타입스크립트는 function 키워드가 아닌 => 기호로 만드는 화살표 함수도 제공한다.

const 함수 이름 = (매개변수 1: 타입1, 매개변수2: 타입2[, ...]) : 반환타입 => 함수 몸통

그런데 화살표 함수의 몸통은 function 때와는 다르게 다음처럼 중괄호를 사용할 수도 있고 생략할 수도 있다.

const arrow1 = (a: number, b: number): number =>{return a + b}; // 실행문 방식 몸통
const arrow1 = (a: number, b: number): number => a + b; // 표현식 문 방식 몸통

그런데 흥미롭게도 중괄호 사용 여부에 따라 타입스크립트 문법이 동작하는 방식이 실행문 방식과 표현식 문 방식으로 달라진다.

실행문과 표현식 문

꽤 오래전부터 프로그래밍 언어는 실행문 지향 언어와 표현식 지향 언어로 구분되어 왔다. C가 대표적인 실행문 지향 언어이고, 스칼라가 대표적인 표현식 지향 언어이다. 자바스크립트는 흥미롭게도 ES5는 실행문 지향 언어지만, ESNext와 타입스크립트는 실행문과 표현식 문을 동시에 지원한다. 보통 이런 언어를 '다중 패러다임 언어' 라고 한다.
프로그래밍 언어에서 실행문은 CPU에서 실행되는 코드를 의미한다. 그런데 실행문은 CPU에서 실행만 될 뿐 결과를 알려주지 않는다. 실행문이 실행된 결과를 알려면 반드시 return 키워드를 사용해야 한다. 반면 표현식 문은 CPU에서 실행된 결과를 굳이 return 키워드를 사용하지 않는다.

코드로 한번 알아보자. 다음처럼 변수에 값을 대입하는 것은 대표적인 실행문이다.
이런 코드는 변수 x에 값 1을 설정하는 작업만으로 충분하다.

let x
x = 1

반면 다음과 같은 코드에서 x > 0 부분은 CPU가 평가한 후 true나 false라는 값으로 결과를 알려주지 않으면 if문이 정상적으로 동작할 수 없다.

let x = 10
if(x > 0)
  x = 1

그런데 만일 프로그래밍 문법이 다음과 같다면 코드를 작성하기가 상당히 번거로워진다

if(return x > 0)
  x = 1

즉, 똑같이 CPU에서 실행되는 구문이더라도 x > 0 처럼 return 키워드 없이 결괏값을 반환하는 실행문이 필요하다. 이를 '표현식 문'이라고 구분해서 부른다.

복함 실행문

프로그래밍 언어에서 if와 같은 구문은 다음처럼 조건을 만족하면 단순히 한 줄의 실행문만을 실행하는 형태로 설계한다.

if(조건식)
  실행문

이런 설계가 가능한 이유는 복합 실행문이라는 또 다른 형태를 함께 제공하기 때문이다. 대부분 언어에서 복합 실행문은 중괄호 {}를 사용해 다음처럼 이용한다.

if(조건식){
  실행문1,
  실행문2
}

복합 실행문은 컴파일러로 하여금 여러 실행문을 한 개처럼 인식하게 한다. 따라서 컴파일러는 앞으리 형태로 작성된 if문은 여전히 한줄의 실행문으로 인식한다.

함수 몸통과 복합 실행문

function 키워드로 만드는 함수는 반드시 몸통을 중괄호 {}로 감싸야 한다. 여기서 중괄호는 앞서 설명한 복합 실행문을 의미한다. 따라서 함수 몸통은 다음처럼 여러 줄로 구현할 수 있다.

let x = 1, y = 2;
let result = x + y + 10;

return 키워드

앞서 설명한 대로 실행문은 CPU에서 실행된 결과를 알려주지 않는다. 예를 들어 함수 몸통을 복합 실행문으로 구현한 다음 함수는 true나 false를 반환하지 않는다.

function isGreater(a: number, b; number): boolean {
	a > b // 결과를 반환하지 않음
}

실행문 기반 언어는 이 문제를 해결하려고 return이라는 키워드를 도입하였다.

function isGreater(a: number, b; number): boolean {
	return a > b // true or false
}

return 키워드는 반스디 함수 몸통에서만 사용할수 있다. 이러한 제약은 문법을 잘못 이해하여 다음과 같은 코드를 작성하는것을 방지하려는 의도이다.

if(return x > 0) x = 1

표현식 문 스타일의 화살표 함수 구현

앞서 function 스타일 함수 isGreater를 화살표 함수로 구현하면 다음과 같다.

const isGreater = (a: number, b: number) : boolean =>{
   return a > b;
}

다만, 단순한 내용을 이렇게 구현하는 것은 번거로우니 ESNext와 타입스크립트는 다음 처럼 구현이 가능하다.

const isGreater = (a: number, b: number) : boolean => a > b;

이 코드는 함수의 몸통이 {a >b}가 아니라 단순히 a > b로 구현되어 있다. 즉, 함수 몸통이 표현식으로 구현되었다. 그리고 표현식은 값을 반환하는 실행문이므로 return 키워드 또한 생략되었다.

표현식과 표현식 문의 차이

표현식문과 표현식의 차이를 알아보자.
아래 코드에서 02행에 있는 a>b 코드는 C언어에서 '표현식'이라고 했기 때문에 그 이후 만들어진 프로그래밍 언어들도 C언어와 같은 의미로 표현식이라고 생각한다. 따라서 C언어 관점에서 실행문의 일부일 뿐 그 자체가 실행문인 것은 아니다. 반면에 표현식 지향 언어 관점에서 03행의 a>b코드는 그 자체가 실행문이다.

표현식과 표현식 문의 차이

let a = 1, b = 0;
if(a>b) console.log('a is greater than b')
const isGreater = (a:number, b:number):boolean => a > b

결론적으로 '표현식'이란 용어는 두 가지 형태로 사용되는데, 이 둘을 구분하고자 표현식과 표현식 문으로 구분한 것이다.

실행문을 만드는 세미콜론

C언어는 모든 문장이 반드시 세미콜론(;)으로 끝나야 한다. C언어 구문을 참조해 만든 ES5 자바스크립트 또한 모든 문장 끝에 세미콜론이 있어야한다. 반면 ESNext 자바스크립트와 타입스크립트에서는 세미콜론을 생략할 수있다. 다만 타입스크립트에서는 관습적으로 표현식 문에는 세미콜론을 붙이지 않는다.

04-4 일등 함수 살표보기

콜백 함수

일등 함수는 프로그래밍 언어가 제공하는 기능이다. 일등 함수 기능을 제공하는 언어에서 함수는 '함수 표현식'이라는 일종의 값이다.따라서 변수에 담을 수 있다. 이 말을 함수 표현식을 매개변수로 받을 수 있다는 것을 의미한다. 이처럼 매개변수 형태로 동작하는 함수를 콜백 함수 라고 한다.
다음 코드에서 함수 f는 callbakc이라는 매개변수가 있는데, 함수 몸통에서 함수로서 호출한다.

const f = (callback: () => void) : void => callback()

다음 코드는 좀 더 현실적인 콜백 함수 사용 예이다. init 함수는 중간에 매개변수로 받은 callback에 담긴 함수 표현식을 실행한다.

init.ts

export const init = (callback:() => void) : void =>{
  console.log('default initaliztion finished');
  callback();
  console.log('all initialization finished.');
}  

다음 코드는 앞서 구현한 init함수에 자신이 실행하려는 내용을 익명 함수로 전달한다.
callback.ts

import {init} from './init'
init(() => console.log('custom initialization finished.'));

실행결과

default initaliztion finished
custom initialization finished.
all initialization finished.

실행 결과를 보면 init 함수가 자신의 몸통과 외부에서 전달받은 함수를 호출해 각각의 출력문이 실행된 것을 확인할 수 있다.

중첩 함수

함수형 언어에서 함수는 변수에 담긴 함수 표현식이므로 함수 안에 또 다른 함수를 중첩해서 구현할 수 있다. 다음 코드에서 calc 함수는 add와 multiply라는 이름의 중첩 함수를 구현하고 있다.
nested.ts

const calc = (value:number, cb:(number) => void):void =>{
    let add = (a,b) => a+b;
    function multiply(a,b) {return a * b}
    let result = multiply(add(1,2),value)
    cb(result)
  }
    calc(30,(result:number) => console.log(`result is ${result}`)) // result is 90

고차 함수와 클로저, 그리고 부분 함수

고차 함수는 또 다른 함수를 반환하는 함수를 말한다. 함수형 언어에서 함수는 단순히 함수 표현식이라는 값이므로 다른 함수를 반환할 수 있다. 고차 함수기능이 없다면 함수형 프로그래밍이 불가능할 정도로 고차 함수는 매우 중요한 기능이다.
먼저 고차함수의 일반적인 형태를 보자

const add1 = (a:number, b: number) : number => a + b; // 보통 함수
const add2 = (a:number): (number) => (b:number): number => a + b // 고차 함수

add1은 일반적인 함수로 선언되었지만 add2는 고차 함수로 선언되었다.. 다음 코드에서 add 함수의 앞의 add2함수를 이름만 바꾼 것이다. 고차 함수가 흥미로운 것은 02행에 있는 함수 호출 부분이다.

high-order.ts

const add = (a:number) : (number) => number => (b: number) : number => a + b
const result = add(1)(2)
console.log(result) //3

이런 구문이 어떻게 가능한지 add함수를 좀 더 이해하기 쉬운 형태로 다시 구현해보자.
다음 코드는 number 타입의 매개변수를 받아 number 타입의 값을 반환하는 함수 시그니처를 NumberToNumberFunc 타입의로 정의한다.

type NumberToNumberFunc = (number) => number

이제 NumberToNumberFunc 타입의 함수를 반환하는 add와 같은 함수를 만들 수 있다.

type NumberToNumberFunc = (number) => number
export const add = (a:number) : NumberToNumberFunc =>{
  // NumberToNumberFunc 타입의 함수 반환
}

다음으로 add의 반환값을 중첩 함수로 구현할수 있다.

type NumberToNumberFunc = (number) => number
export const add = (a:number) : NumberToNumberFunc =>{
  const _add: NumberToNumberFunc = (b:number): number =>{
    // number 타입의 값 반환
  }
}

add함수가 반환하는 _add는 NumberToNumberFunc 타입이다. 고차 함수는 이처럼 중첩함수를 반환 할 수 있다.
이제 최종적으로 _add의 몸통을 구현하면 다음처럼 add라는 이름의 고차함수가 완성된다.

add.ts

type NumberToNumberFunc = (number) => number
export const add = (a:number) : NumberToNumberFunc =>{
  const _add: NumberToNumberFunc = (b:number): number =>{
    return a + b // 클로저
  }
  return _add
}

04행(return a + b)이 흥미로운 것은 a는 add함수의 매개변수이고 b는 _add함수의 매개변수라는 사실이다. 즉, _add함수의 관점에서만 보면 a는 외부에 선언된 변수이다. 함수형 프로그래밍 언어에서는 04행과 같은 형태를 클로저(closure)라고 한다. 고차 함수는 이 클로저 기능이 반드시 필요하다.
이제 지금까지 구현한 고차 함수 add를 사용하는 코드를 살펴보자. 앞서 수현한 add는 NumberToNumberfunc 타입의 값을 반환하는 함수이므로 다음과 같은 코드를 작성할 수 있다.

import {NumberToNumberFunc, add} from './add'
let fn: NumberToNumberFunc = add(1)

그런데 변수 fn에 담긴 값은 NumberToNumberFunc 타입의 함수 표현식 이므로 다음 05행 처럼 fn 뒤에 함수 호출 연산자를 붙일 수 있다.

import {NumberToNumberFunc, add} from './add'
let fn: NumberToNumberFunc = add(1);
let result = fn(2)
console.log(result) // 3
console.log(add(1)(2)) // 3

코드를 주의 깊게 관찰하면 변수 fn은 단순히 add(1)을 저장하는 임시 변수의 역할만 한다. 따라서 fn과 같은 임시 변수를 사용하지 않는다면 07행과 같은 구문이 된다. 2차 고차 함수인 add는 add(1)(2) 함수 호출 연산자를 두 개 사용해야만 함수가 아닌 값을 얻을 수 있다.
만일, add가 다음 multiply처럼 3차 고차 함수로 구현되어있다면 multiply(1)(2)(3)처럼 함수 호출 연산자가 3개 필요합니다.

const multiply = a => b => c => a * b * c

그리고 3차 고차 함수인 multiply에 함수 호출 연산자를 하나나 두 개만 붙여 multiply(1)이나 multiply(1)(2)처럼 사용하면 아직 값이 아닌 함수이다. 이것을 '부분 애플리케이션' 혹은 '부분 적용 함수'라고 한다.

04-5 함수 구현 기법

매개변수 기본값 지정하기

앞서 04-1절에서 선택적 매개변수를 설명했다. 선택적 매개변수는 항상 그 값이 undefined로 고정된다. 만일, 함수 호출 시 인수를 전달하지 않더라도 매개변수에 어떤 값을 설정하고 싶다면 매개변수의 기본값을 지정할 수 있다. 이를 디폴트 매개변수라 하고 다음과 같은 형태로 사용한다.

(매개변수: 타입 = 매개변수 기본값)

다음 코드에서 03행의 makePerson 함수는 호출 때 매개변수 age에 해당하는 값을 전달받지 못하면 기본으로 10이 설정된다.
default.ts

exrpot type Person = {name: string, age: number}

export const makePerson = (name:string, age:number = 10) : Person =>{
  const person : Person = {name : name, age: age};
  return person
}

console.log(makePerson('Jack'));
console.log(makePerson('Jack',33));

객체 생성 시 값 부분을 생략할 수 있는 타입스크립트 구문

타입스크립트는 다음처럼 매개변수의 이름과 똑같은 이름의 속성을 가진 객체를 만들수 있다.
이때 속성값 부분을 생략할 수 있는 단축 구문을 제공한다.

const makePerson = (name:string, age: number) =>{
	const person = {name, age} //{name:name, age:age}의 단축 표현
}

다음 return-object.ts 파일의 구현 내용은 앞에서 구현한 default.ts의 내용과 04행만 다르다. 객체 관련 단축 구문이 적용되어 코드가 조금이나마 간결해졌다.\
return-object.ts

exrpot type Person = {name: string, age: number}

export const makePerson = (name:string, age:number = 10) : Person =>{
  const person : Person = {name, age}
  return person
}

console.log(makePerson('Jack'));
console.log(makePerson('Jack', 33));

객체를 반환하는 화살표 함수 만들기

화살표 함수에서 객체를 반환하고자 할 때는 엇핏 다음과 같은 코드를 생각할 수 있다.

export const makePerson = (name:string, age: number = 10): Person => {name, age}

하지만 이렇게 구현하면 컴파일러는 중괄호 {}를 객체가 아닌 복합 실행문으로 해석한다. 컴파일러가 {}를 객체로 해석하게 하려면 다음처럼 객체를 소괄호로 감싸주어야 한다.

export const makePerson = (name: string, age: number = 10): Person => ({name, age})

다음은 이러한 내용을 반영해 앞에서 구현한 return-object.js를 좀 더 간결하게 구현한 예이다.
arrow.ts

exrpot type Person = {name: string, age: number}

export const makePerson = (name:string, age:number = 10) : Person =>({name, age})

console.log(makePerson('Jack'));
console.log(makePerson('Jack', 33));

매개변수에 비구조화 할당문 사용하기

앞서 03-4절에서는 객체에 비구조화 할당문을 적용하는 내용을 다뤘다. 그런데 함수의 매개변수도 변수의 일종이므로 다음처럼 비구조화 할당문을 적용할 수 있다.
destructuring.ts

export type Person = {name:string, age:number}

const printPerson = ({name,age}:Person): void =>{
  console.log(`name: ${name}, age: ${age}`)
}

printPerson({name:'Jack', age: 10});

색인 키와 값으로 객체 만들기

ESNext 자바스크립트에서는 다음과 같은 코드를 작성할 수 있다.

const makeObject = (key, value) => ({[key]: value})

이 코드는 다음처럼 객체의 속성 이름을 변수로 만들려고 할때 사용한다. 즉, [key] 부분이 'name'이면 {name:value}, 'firstName'이면 {firstName:value} 형태의 객체를 생성한다.

const makeObject = (key, value) => ({[key]: value})
console.log(makeObject('name', 'Jack')) // { name: 'Jack' }
console.log(makeObject('firstname', 'Jane')) // { firstname: 'Jane' }

타입스크립트에서는 {[key]: value} 형태의 타입을 '색인 가능 타입'이라고 하며, 다음과 같은 형태로 key와 value 타입을 명시한다.

type KeyType={
	[key:string]:string
}

다음 코드는 색인 가능 타입을 사용해 속성 이름만 다른 객체를 만드는 예이다.

export type KeyValuetype={
  [key:string]: string
}
export const makeObject = (key:string, value: string): KeyValueType =>({[key]:value})

console.log(makeObject('name', 'Jack'));
console.log(makeObject('firstName', 'Jane'));

04-6 클래스 메서드

function 함수와 this 키워드
앞서 04-2절에서 타입스크립트의 function 키워드로 만든 함수는 Function이란 클래스의 인스턴스, 즉 함수는 객체라고 하였다. 객체지향 언어에서 인스턴스는 this키워드를 사용 할 수 있다. 타입스크립트에서는 function 키워드로 만든 함수에 this 키워드를 사용 할 수 있다. 반면 화살표 함수에서는 this 키워드를 사용할수 없다.

메서드란?

타입스크립트에서 메서드(method)는 function으로 만든 함수 표현식을 담고 있는 속성이다. 다음 코드에서 클래스 A는 value와 method라는 두 개의 속성을 가진다. value에는 1이라는 값을 설정하지만, method는 ()=>void 타입의 함수 표현식을 설정한다. 여기서 method 구현 내용 중 특이한 부분은 04행의 this.value 부분이다.
A.ts

export class A{
  	value: number = 1;
  	method: () => void = function(): void {
      console.log(`value : ${this.value}`);
    }
}

이제 다음과 같은 테스트 코드를 만들어 실행해 보자. A.ts의 02행에서 value 속성을 1로 설정했으므로 04행의 this.value는 1이 되어 value: 1 이라는 문자열이 출력된다.
testA.ts

import { A } from './A'
let a : A = new A;
a.method();

클래스 메서드 구문

앞에서 작성한 클래스 A는 구현하기도 번고롭고 가독성도 떨어진다. 타입스크립트는 클래스 속성 중 함수 표현식을 담는 속성은 function 키워드를 생략할 수 있게 하는 단축 구문(shorthand)을 제공한다.
다음 코드에서 B클래스는 타입스크립트 답게 구현한 클래스 A이다. A와 B는 똑같이 동작하지만 B 코드가 더 간결하다.
B.ts

export class B {
    constructor(public value: number = 1) { }
    method(): void {
        console.log(`value : ${this.value}`);
    }
}

다음은 B클래스를 테스트하는 코드이다. 실행해 보면 B클래스의 생성자를 통해 전달된 2라는 값이 value에 설정되고 method가 호출되어 2라는 값이 출력된다.
testB.ts

import { B } from './B'
let b : B = new B(2);
b.method();

정적 메서드

클래스의 속성은 static 수정자(modifier)를 속성 앞에 붙여서 정적으로 만들 수 있다.
메서드 또한 속성이므로 이름 앞에 static 수정자를 붙여 정적 메서드를 만들 수 있다.
다음 코드는 C와 D라는 두 클래스가 whoAreYou라는 같은 이름의 정적 메서드를 구현하고 있다. 클래스 메서드는 13, 14행에서 보듯 '클래스 이름.정적 메서드'형태로 호출한다.
static-method.ts

export class C {
  static whoAreYou(): string {
    return `I'm class C`
  }
}

export class D{
  static whoAreYou(): string{
    return `i'm class D`
  }
}

console.log(C.whoAreYou());
console.log(D.whoAreYou());

메서드 체인

제이쿼리(jQuery)와 같은 라이브러리는 다음처럼 객체의 메서드를 이어서 계속 호출하는 방식의 코드를 작성할 수 있다. 이러한 방식을 메서드 체인(method chain)이라고 한다.

$("#p1").css("color","red").slideUp(2000).slideDown(2000);

타입스크립트로 메서드 체인을 구현하려면 메서드가 항상 this를 반환하게 한다.
method-chain.ts

export class calculator {
  constructor(public value: number =0){}
  add(value : number){
    this.value += value;
    return this
  }
  multiply(value : number){
    this.value *= value;
    return this
  }
}

이제 다음과 같은 테스크 토드에서 04행처럼 제이쿼리 스타일로 구현할 수 있다.
test-methid-chain.ts

import {Calculator} from './method-chain'

let calc =new Calculator;
let result = calc.add(1).add(2).multiply(3).multiply(4).value
console.log(result) // (0 + 1 + 2) * 3 * 4 = 36

지금까지 살펴본 코드 설계 기법은 다음 05장의 배열에서 폭넗게 확인할 수 있다.

profile
느린 걸음도 먼 길을 갈 수 있다.

0개의 댓글