함수형 프로그래밍에서 배열은 매우 중요한 기능을 한다. 이번에는 순수 함수를 다루면서 배열과 튜플에 대해 살펴보자.
5-1 배열 이해하기
자바스크립트에서 배열은 Array 클래스의 인스턴스이며 다음처럼 선언한다.
let 배열 이름 = new Array(배열 길이)
배열에 담긴 각각의 값을 아이템(item) 혹은 원소(element)라고 한다.
[ ]단축구문
자바스크립트는 [ ]라는 단축 구문을 제공한다..
let numbers = [1,2,3];
let strings = ["Hello", "World"];
console.log(numbers, strings) // [1,2,3] ["Hello","World"]
자바스크립트에서 배열은 객체다
자바스크립트에서 배열은 다른 언어와 다르게 객체이다. 배열은 Array 클래스의 인스턴스인데, 클래스의 인스턴스는 객체이기 때문이다.
배열의 타입
타입스크립트에서 배열의 타입은 “아이템 타입[]”이다. 예를 들어, 배열의 아이템이 number 타입이면 배열의 타입은 number[]이고, 아이템이 string 타입이면 string[]이다.
let numArray: number[] = [1, 2, 3]
let strArray: string[] = ['Hello', 'World']
type IPerson = { name: string; age?: number }
let personArray: IPerson[] = [{ name: 'Jack' }, { name: 'Jane', age: 32 }]
문자열과 배열 간 변환
타입스크립트에서는 문자 타입이 없고 문자열의 내용 또한 변경할 수 없다. 이러한 특징 때문에 문자열을 가공하려면 먼저 문자열을 배열로 전환해야 한다. 보통 전환할 때는 string 클래스의 split 메서드를 사용한다.
split(구분자: string): string[]
string[] 타입의 배열을 다시 string 타입으로 변환하려면 Array클래스의 join 메서드를 사용한다.
join(구분자: string): string
인덱스 연산자
배열이 담고 있는 아이템 중 특정 위치에 있는 아이템을 얻고자 할 때는 인덱스 연산자를 사용한다. 인덱스 연산자는 배열의 특정 위치에 있는 아이템을 얻는다.
const numbers2: number[] = [1, 2, 3, 4, 5]
for (let index = 0; index < numbers2.length; index++) {
const item: number = numbers2[index]
console.log(item) // 1 2 3 4 5
}
배열의 비구조화 할당
배열의 비구조화 할당문에서는 객체와 달리 [] 기호를 사용한다.
let array: number[] = [1, 2, 3, 4, 5]
let [first, second, third, ...rest] = array
console.log(first, second, third, rest) // 1 2 3 [ 4, 5 ]
for…in 문
ESNext 자바스크립트와 타입스크립트는 for 문을 좀 더 쉽게 사용하도록 for..in 문을 제공한다. for..in문은 배열의 인덱스값을 순회한다.
let names = ['Jack', 'Jane', 'Steve']
for (let index in names) {
const name = names[index]
console.log(`[${index}]: ${name}`) // [0]: Jack [1]: Jane [2]: Steve
}
/*
다음 코드는 아래 오류 메시지를 발생하며,
error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ name: string; age: number; }'.
해결 방법은 아래 url의 의미처럼 jack 객체의 index 타입을 명시해 주는 것
*/
// <https://stackoverflow.com/questions/56833469/typescript-error-ts7053-element-implicitly-has-an-any-type>
// let jack = { name: 'Jack', age: 32 }
let jack: { [index: string]: any } = { name: 'Jack', age: 32 }
for (let property in jack) {
console.log(`${property}: ${jack[property]}`) // name: Jack age: 32
}
for…of 문
for…in문은 배열의 인덱스값을 대상으로 순회하지만, for…of 문은 배열의 아이템값을 대상으로 순회한다.
for (let name of ['Jack', 'Jane', 'Steve'])
console.log(name) // Jack Jane Steve
제네릭 방식 타입
배열을 다루는 함수를 작성할 때는 number[]와 같이 타입이 고정된 함수를 만들기보다는 T[] 형태로 배열의 아이템 타입을 한꺼번에 표현하는 것이 편리하다. 타입을 T와 같은 일종의 변수(타입 변수)로 취급하는 것을 제네릭 타입이라고 한다.
const arrayLength = (array: T[]): number => array.length
다양한 아이템 타입을 가지는 배열에 똑같이 적용되게 하려면 배열의 타입 주석을 T[]로 표현한다. 그런데 이렇게 하면 컴파일러가 T의 의미를 알 수 있어야 한다. 배열의 길이를 구하는 함수와 배열이 비었는지를 판별하는 함수를 구현하면 다음과 같다.
export const arrayLength = <T>(array: T[]): number => array.length
export const isEmpty = <T>(array: T[]): boolean => arrayLength<T>(array) == 0
import { arrayLength, isEmpty } from './arrayLength'
let numArray: number[] = [1, 2, 3]
let strArray: string[] = ['Hello', 'World']
type IPerson = { name: string; age?: number }
let personArray: IPerson[] = [{ name: 'Jack' }, { name: 'Jane', age: 32 }]
console.log(
arrayLength(numArray), // 3
arrayLength(strArray), // 2
arrayLength(personArray), // 2
isEmpty([]), // true
isEmpty([1]) // false
)
제네릭 함수의 타입 추론
const identity = <T>(n: T): T => n
console.log(
identity<boolean>(true), // true
identity(true) // true
)
제네릭 형태로 구현된 함수는 원칙적으로 타입 변수를 다음과 같은 형태로 명시해 주어야 한다.
함수이름 <타입 변수>(매개변수)
제네릭 함수의 함수 시그니처
타입스크립트는 어떤 경우 함수 시그니처의 매개변수 부분에 변수 이름을 기입하라고 요구한다.
const f = <T>(cb: (arg: T,i?: number) => number): void => {}
전개 연산자
전개 연산자는 배열에도 적용할 수 있다.
let array1: number[] = [1]
let array2: number[] = [2, 3]
let mergedArray: number[] = [...array1, ...array2, 4]
console.log(mergedArray) // [1, 2, 3, 4]
range 함수 구현
range 함수는 재귀 함수 스타일로 동작한다.
export const range = (from: number, to: number): number[] =>
from < to ? [from, ...range(from + 1, to)] : []
import { range } from './range'
let numbers: number[] = range(1, 10)
console.log(numbers) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
5-2 선언형 프로그래밍과 배열
배열은 선언형 프로그래밍을 구현할 때 절대적으로 필요한 문법 기능이다. 선언형 프로그래밍은 곧잘 명령형 프로그래밍과 비교되지만, 이 둘은 대등하게 비교할 대상은 아니다. 명령형은 좀 더 CPU 친화적인 저수준(low-level) 구현 방식이고, 선언형은 명령형 방식 위에서 동작하는 인간에게 좀 더 친화적인 고수준(high-level)구현 방식이다.
명령형 프로그래밍이란?
프로그램의 기본 형태는 다음처럼 입력 데이터를 얻고 가공한 다음, 결과를 출력하는 형태로 구성된다.
입력 데이터 얻기
입력 데이터 가공해 출력 데이터 생성
출력 데이터 출력
명령형 프로그래밍에서는 여러 개의 데이터를 대상으로 할 때 다음처럼 for 문을 사용한다.
for(; ;) {
입력 데이터 얻기
입력 데이터 가공해 출력 데이터 생성
출력 데이터 출력
}
반면, 선언형 프로그래밍은 시스템 자원의 효율적인 운용보다는 일괄된 문제 해결 구조에 집중한다. for문을 사용하지 않고 모든 데이터를 배열에 담으며 해결될 때가지 끊임없이 다른 형태의 배열로 가공한는 방식으로 구현한다.
문제를 푸는 데 필요한 모든 데이터 배열에 저장
입력 데이터 배열을 가공해 출력 데이터 배열 생성
출력 데이터 배열에 담긴 아이템 출력
1부터 100까지 더하기 문제 풀이
명령형 프로그래밍 방식
let sum = 0
for (let val = 1; val <= 100; )
sum += val++
console.log(sum) // 5050
선언형 프로그래밍 방식
import { range } from './range'
import { fold } from './fold'
let numbers: number[] = range(1, 100 + 1)
console.log(numbers) // [1,2,...,100]
선언형은 데이터 생성과 가공 과정을 분리한다. 이제 배열에 담긴 데이터를 모두 더해보자.
fold: 배열 데이터 접기
폴드는 [,1,2,3,…] 형태의 배열 데이터를 가공해 5050과 같은 하나의 값을 생성하려고 할 때 사용한다. 배열의 아이템 타입이 T라고 할 때 배열은 T[]로 표현할 수 있는데, 폴드 함수는 T[] 타입 배열을 가공해 T 타입 결과를 만들어 준다.
export const fold = <T>(array: T[], callback: (result: T, val: T) => T, initValue: T) => {
let result: T = initValue
for (let i = 0; i < array.length; ++i) {
const value = array[i]
result = callback(result, value)
}
return result
}
import { range } from './range'
import { fold } from './fold'
let numbers: number[] = range(1, 100 + 1)
let result = fold(numbers, (result, value) => result + value, 0)
console.log(result) // 5050
에서 100까지 홀수의 합 구하기
이제 범용적이고 재사용 가능 이라는 관점에서 또 다른 문제를 풀어보자. 1에서 100 까지 숫자 중 홀수만 더하는 문제이다. 명령형 방식으로 구현해 보면
let oddSum = 0
for (let val = 1; val <= 100; val += 2) oddSum += val
console.log(oddSum) // 2500
선언형 방식의 토드를 생각해 보면 앞서 구현한 fold는 배열 데이터를 값으로 만들어 주는 기능을 수행할 뿐 배열 데이터에서 홀수만 추려내는 기능은 없다. 따라서 조건을 만족하는 아이템만 추려내는 filter라는 함수를 만들어보자.
filter: 조건에 맞는 아이템만 추려내기
export const filter = <T>(array: T[], callback: (value: T, index?: number) => boolean): T[] => {
let result: T[] = []
for (let index: number = 0; index < array.length; ++index) {
const value = array[index]
if (callback(value, index)) result = [...result, value]
}
return result
}
import { range } from './range'
import { fold } from './fold'
import { filter } from './filter'
let numbers: number[] = range(1, 100 + 1)
const isOdd = (n: number): boolean => n % 2 != 0
let result = fold(filter(numbers, isOdd), (result, value) => result + value, 0)
console.log(result) // 2500
이 파일은 numbers를 filter 함수를 사용해 홀수만 추려낸 다음, 비로소 배열의 합을 구하는 로직을 적용했다.
1에서 100까지 짝수의 합 구하기
1에서 100까지 홀수의 합이 2,500이면 짝수의 합은 5050 - 2500 = 2550이다. 명령형 방식으로 코드를 작성해보자.
let evenSum = 0
for (let val = 0; val <= 100; val += 2) evenSum += val
console.log(evenSum) // 2550
다음은 선언형 방식으로 구해보자. 이미 구현해 둔 fold와 filter 함수를 재사용하고 로직도 재사용 해 보자.
import { range } from './range'
import { fold } from './fold'
import { filter } from './filter'
let numbers: number[] = range(1, 100 + 1)
const isEven = (n: number): boolean => n % 2 == 0
let result = fold(filter(numbers, isEven), (result, value) => result + value, 0)
console.log(result) // 2550
제곱근 구하기
이번에는 입력 데이터 자체를 모두 가공하는 형태의 문제를 풀어보자. 다음 코드는 배열의 각 아이템을 곱한 뒤 모두 더하는 계산을 명령형 방식으로 구현한 것이다.
let squareSum = 0
for (let val = 1; val <= 100; ++val) squareSum += val * val
console.log(squareSum) // 338350
선언형 방식으로 구현하려면 map이라는 이름의 함수가 필요하다.
map: 배열 데이터 가공하기
선언형 방식으로 구하려면 [1,2,…] 형태의 입력 데이터를 [12, 22,…] 형태로 가공해 주는 함수가 필요하다. 이런 기능을 구현하려면 map이라는 함수를 이용한다.
export const map = <T, Q>(array: T[], callback: (value: T, index?: number) => Q): Q[] => {
let result: Q[] = []
for (let index = 0; index < array.length; ++index) {
const value = array[index]
result = [...result, callback(value, index)]
}
return result
}
이제 map 함수를 이용해 선언형 방식의 코드를 작성하면
import { range } from './range'
import { fold } from './fold'
import { map } from './map'
let numbers: number[] = range(1, 100 + 1)
let result = fold(
map(numbers, value => value * value),
(result, value) => result + value,
0
)
console.log(result) // 338350
5-3 배열의 map, reduce, filter 메서드
//const multiply = (result, val) => result * val // 7번 줄에서 사용됩니다.
const multiply = (result: number, val: number) => result * val // 7번 줄에서 사용됩니다.
let numbers: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let tempResult = numbers
.filter((val) => val % 2 != 0)
.map((val) => val * val)
.reduce(multiply, 1)
let result = Math.round(Math.sqrt(tempResult))
console.log(result) // 13
앞으로 자주 볼 전형적인 메서드 체인 방식이다.
filter 메서드
배열의 타입이 T[]일 때 배열의 filter 메서드는 당므과 같은 형태로 설계되어있다.
filter(callback: (value: T, index?: number): boolean): T[]
import { range } from './range' // 05-1의 range입니다
const array: number[] = range(1, 10 + 1)
let odds: number[] = array.filter(value => value % 2 != 0)
let evens: number[] = array.filter(value => value % 2 == 0)
console.log(odds, evens) // [ 1, 3, 5, 7, 9 ] [ 2, 4, 6, 8, 10 ]
filter 메서드는 두 번째 매개변수에 index라는 선택 속성을 제공한다. 다음 코드는 index 값을 사용해 배열을 반으로 나누는 예이다.
import { range } from './range'
const array: number[] = range(1, 10 + 1)
const half = array.length / 2
let belowHalf: number[] = array.filter((v, index) => index < half)
let overHalf: number[] = array.filter((v, index) => index >= half)
console.log(belowHalf, overHalf) // [ 1, 2, 3, 4, 5 ] [ 6, 7, 8, 9, 10 ]
map 메서드
배열의 타입이 T[]일 때 배열의 map 메서드는 다음과 같은 형태로 설계되어있다.
map(callback: (value: T, index?: number): Q): Q[]
다음 코드는 위에서 구현한 map 함수의 내용을 배열의 map 메서드로 다시 구현한 예이다.
import { range } from './range'
let squres: number[] = range(1, 5 + 1).map((val: number) => val * val)
console.log(squres) //[ 1, 4, 9, 16, 25 ]
다음 코드는 number[] 타입 배열을 string[] 타입 배열로 가공한 예이다.
import { range } from './range'
let names: string[] = range(1, 5 + 1).map((val, index) => `[${index}]: ${val}`)
console.log(names) // [ '[0]: 1', '[1]: 2', '[2]: 3', '[3]: 4', '[4]: 5' ]
reduce 메서드 앞서 구현한 fold 함수는 타입스크립트 배열의 reduce 메서드로 대체할 수 있다.
reduce(callback: (result: T, value: T), initialValue: T): T
다음 코드는 1부터 100까지 더하는 로직을 reduce 메서드를 사용해 구현한 예이다.
import { range } from './range'
let reduceSum: number = range(1, 100 + 1).reduce(
(result: number, value: number) => result + value,
0
)
console.log(reduceSum) // 5050
만약 , 배열의 각 아이템을 모두 곱하고 싶다면 다음처럼 구현할 수 있다.
import { range } from './range'
let reduceSum: number = range(1, 10 + 1).reduce(
(result: number, value: number) => result * value,
1
)
console.log(reduceSum) // 3628800
주의할 점은 어떤 수에 0을 곱하면 결과는 0이므로 reduce 메서드의 두 번째 인수는 0이 아니ㅏㄹ 1을 전달해야 한다.
5-4 순수 함수와 배열
함수형 프로그래밍에서 함수는 **순수 함수(pure function)**라는 조건을 만족해야 한다. 하지만 타입스크립트의 Array 클래스에는 순수 함수 조건에 부합하지 않는 메서드가 많다. 따라서 타입스크립트로 함수형 프로그래밍을 하면서 배열의 메서들르 사용할 때는 해당 메서드가 어떤 특성이 있는지 살펴야 한다.
순수 함수란?
순수 함수는 부수 효과(side-effect)가 없는 함수를 말한다. 여기서 부수 효과란 함수가 가진 고유한 목적 이외에 다른 효과가 나타나는 것을 의미하며 부작용이라고도 한다. 반면에 부수 효과가 있는 함수는 불순 함수(impure funciton) 라고 한다.
부수 효과는 함수를 순수 함수 형태로 작성해야만 제거할 수 있다. 어떤 함수가 부수 효과가 없는 순수한 함수이려면 다음과 같은 조건을 충족해야 한다.
함수 몸통에 입출력 관련 코드가 없어야 한다.
함수 몸통에서 매개변숫값을 변경시키지 않는다(즉, 매개변수는 const나 readonly 형태로만 사용한다).
함수는 몸통에서 만들어진 결과를 즉시 반환한다.
함수 내부에 전역 변수나 정적 변수를 사용하지 않는다.
함수가 예외를 발생시키지 않는다.
함수가 콜백 함수로 구현되었거나 함수 몸통에 콜백 함수를 사용하는 코드가 없다.
함수 몸통에 Promise와 같은 비동기 방식으로 동작하는 코드가 없다.
pure 함수는 이런 조건을 모두 만족하는 순수 함수이다.
function pure(a: number, b: number): number {
return a + b
}
다음 impure1 함수는 매개변수를 변경하므로 부수 효과가 발생한다.
function impure1(array: number[]): void{
array.push(1);
array.splice(0,1);
}
- array 매개변수로 전달받은 배열은 push와 splice 메서드를 호출함으로써 내용이 달라진다. 즉, 매개변수가 readonly 형태로 동작하지 않으므로 불순 함수이다.
타입 수정자 readonly
readonly 타입으로 선언된 매개변숫값을 변경하는 시도가 있으면 문제가 있는 코드라고 알려줘서 불순 함수가 되지 않게 방지한다.
불변과 가변
변수가 const나 readonly를 명시하고 있으면 변숫값은 초깃값을 항상 유지한다. 이번 변수는 변경할 수 없다는 의미로 불변 변수라고 한다. 반면에 명시하지 않은 변수는 언제든 값을 변경할 수 있다는 의미로 가변 변수라고 한다.
깊은 복사와 얕은 복사
어떤 변숫값을 다른 변숫값으로 설정하는 것을 복사라고 표현한다. 순수 함수를 구현할 때는 매개변수가 불변성을 유지해야 하므로, 매개변수를 가공하려고 할 대 깊은 복사를 실행해 매개변숫값이 변경되지 않게 해야 한다.
깊은 복사는 대상 변숫값이 바귈 때 원본 변숫값은 그대로인 형태로 동작한다.
let original = 1
let copied = original
copied += 2
console.log(original, copied) // 1 3
그러나 객체와 배열은 얕은 복사 방식으로 동작한다. 코드를 실행해 보면 얕은 복사가 된 shallowCopiedArray가 내용을 변경하면 원본 배열 또한 변경되는 것을 확인할 수 있다.
const originalArray = [5, 3, 9, 7]
const shallowCopiedArray = originalArray
shallowCopiedArray[0] = 0
console.log(originalArray, shallowCopiedArray) // [ 0, 3, 9, 7 ] [ 0, 3, 9, 7 ]
전개 연산자와 깊은 복사
흥미롭게도 전개 연산자를 사용해 배열을 복사하면 깊은 복사를 할 수 있다.
const oArray = [1, 2, 3, 4]
const deepCopiedArray = [...oArray]
deepCopiedArray[0] = 0
console.log(oArray, deepCopiedArray) // [ 1, 2, 3, 4 ] [ 0, 2, 3, 4 ]
배열의 sort 메서드를 순수 함수로 구현하기
sort 메서드는 배열의 아이템을 오름차순 혹은 내림차순으로 정렬해 준다. 하지만 이 메서드는 원본 배열의 내용을 변경한다. 다음 pureSort 함수는 readonly 타입으로 입력 배열의 내용을 유지한 채 정렬할 수 있도록 전개 연산자의 깊은 복사 기능을 사용했다.
export const pureSort = <T>(array: readonly T[]): T[] => {
let deepCopied = [...array]
return deepCopied.sort()
}
다음 테스트 코드는 원본 배열을 변경하지 않으면서 내용이 정렬된 새로운 배열을 얻는다.
import { pureSort } from './pureSort'
let beforeSort = [6, 2, 9, 0]
const afterSort = pureSort(beforeSort)
console.log(beforeSort, afterSort) // [ 6, 2, 9, 0 ] [ 0, 2, 6, 9 ]
배열의 filter 메서드와 순수한 삭제
배열에서 특정 아이템을 삭제할 때는 splice 메서드를 사용한다. 그런데 splice는 원본 배열의 내용을 변경하므로 순수 함수에서는 사용할 수 없다. 하지만 흥미롭게도 특정 아이템을 삭제하는 데 filter 메서드를 사용할 수 있다. 배열이 제공하는 filter와 map메서드는 sort와 다르게 깊은 복사 형태로 동작한다. 따라서 filter 메서드를 사용하면 원본 배열의 내용을 훼손하지 않으면서 조건에 맞지 않는 아이템을 삭제할 수 있다.
export const pureDelete = <T>(array: readonly T[], cb: (val: T, index?: number) => boolean): T[] =>
array.filter((val, index) => cb(val, index) == false)
filter 메서드를 활용해 원본을 훼손하지 않고 조건에 맞는 아이템을 삭제한는 함수다.
다음 코드는 pureDelete 함수를 이용해 배열과 객체서 섞인 원본을 훼손하지 않으면서 배열만 모두 제거한 배열을 만드는 예이다.
import {pureDelete} from './pureDelete'
const mixedArray: object[] = [
[], {name:"Jack"}, {name:"Jane", age: 32}, ["description"]
]
const objectsOnly: object[] = pureDelete(mixedArray, (val) => Array.isArray(val))
console.log(
mixedArray, // [ [], { name: 'Jack' }, { name: 'Jane', age: 32 }, [ 'description' ] ]
objectsOnly // [ { name: 'Jack' }, { name: 'Jane', age: 32 } ]
)
가변 인수 함수과 순수 함수
함수를 호출할 때 전달하는 인수의 개수를 제한하지 않는 것을 가변 인수라고 한다.
import { mergeArray } from './mergeArray'
const mergedArray1: string[] = mergeArray(['Hello'], ['World'])
console.log(mergedArray1) // [ 'Hello', 'World' ]
const mergedArray2: number[] = mergeArray([1], [2, 3], [4, 5, 6], [7, 8, 9, 10])
console.log(mergedArray2) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
mergeArray 처럼 이런 방식으로 동작하는 함수를 가변 인수 함수라고 한다.
이처럼 가변 인수로 호출할 수 있는 mergeArray함수를 구현해 보자. 가변 인수 함수를 구현할 때 기본 형태는 다음과 같다. 매개변수 arrays 앞의 …은 잔여나 전개 연산자가 아니라 가변 인수를 표현하는 구문이다.
export const mergeArray = (...arrays) => {}
mergeArray 함수가 타입에 상관 없이 동작하게 하려면 다음처럼 제네릭 타입으로 구현해야 한다.
export const mergeArray = <T>(...arrays) => {}
또한, mergeArray 함수를 호출할 때 전달하는 값은 모두 배열이었기에 배열로 선언한다.
export const mergeArray = <T>(...arrays: T[][]) => {}
mergeArray 함수의 매개변수 arrays는 배열의 배열인 T[][] 타입일지라도 출력은 T[] 형태의 배열을 반환해야 한다.
export const mergeArray = <T>(...arrays: T[][]): T[] => {}
마지막으로 mergeArray 함수를 순수 함수로 구현하려면 매개변수의 내용을 훼손하지 말아야 한다.
export const mergeArray = <T>(...arrays: readonly T[][]): T[] => {}
지금까지 살펴본 내용을 바탕으로 mergeArray 함수를 구현하면
export const mergeArray = <T>(...arrays: readonly T[][]): T[] => {
let result: T[] = []
for (let index = 0; index < arrays.length; index++) {
const array: T[] = arrays[index]
result = [...result, ...array]
}
return result
}
순수 함수를 고려하면 자바스크립트 배열이 제공하는 많은 메서드를 사용할 수 없다. 하지만 이런 메서드들은 전개 연산자 등의 메커니즘을 사용하면 순수 함수 형태로 간단하게 구현할 수 있다.
5-5 튜플 이해하기
자바스크립트에서는 튜플이 없으며 단순히 배열의 한 종류로 취급된다. 다음 코드는 any 타입 배열을 선언한 예이다.
let tuple: any[] = [true, "the result is ok"]
any[] 형태는 타입스크립트의 타입 기능을 무력화하므로, 타입스크립트는 튜플의 타입 표기법을 배열과 다르게 선언할 수 있다.
const array: number[] = [1,2,3,4]
const tuple: [boolean, string] = [true, "the result is ok"]
튜플에 타입 별칭 사용하기
보통 튜플을 사용할 때는 타입 별칭으로 튜플의 의미를 명확하게 한다.
export type ResultType = [boolean, string]
다음 코드에서 doSomething 함수는 자바스크립트의 try/catch/finally 예외 처리 구문을 사용해 예외가 발생했을 때 구체적인 내용을 튜플로 반환한다.
import { ResultType } from './ResultType'
export const doSomething = (): ResultType => {
try {
throw new Error('Some error occurs...')
} catch (e) {
return [false, e.message]
}
}
이러한 예외 처리 코드는 불순한 함수를 순수 함수로 바꿔주는 전형적인 코드 설계 방식이다.
튜플에 적용하는 비구조화 할당
튜플은 물리적으로는 배열이므로 배열처럼 인덱스 연산자나 비구조화 할당문을 적용할 수 있다.
import { doSomething } from './doSomething'
const [result, errorMessage] = doSomething()
console.log(result, errorMessage) // false Some error occurs...
함수형 프로그래밍에서 배열은 가장 핵심적인 기능이다.
'Books > Do It 타입스크립트 프로그래밍' 카테고리의 다른 글
7장 비동기 콜백 함수 (0) | 2022.12.30 |
---|---|
6장 반복기와 생성기 (0) | 2022.12.12 |
4장 함수와 메서드 (0) | 2022.11.29 |
3장 객체와 타입 (0) | 2022.11.29 |
2장 타입스크립트 프로젝트 생성과 관리 (0) | 2022.11.28 |