일반적으로 우리가 사용하는 타입은 and
로 결합되는 타입입니다.
그 예시를 들자면 아래과 같습니다.
class Person {
string name
int age
}
즉 Person 이라는 타입은 name
과(and) age
로 이루어진 타입이라는 것이죠.
이런 타입 방법은 보편적으로 사용되지만, 만약 Alien
이라는 새로운 타입이 들어온다고 생각해 봅시다.
그리고 Alien
끼리는 서로 이름이 아닌 alienNo
라는 숫자로 부른다고 생각해봅시다.
class Alien{
int alienNo
int age
}
그리고 Person
과 Alien
은 동등한 지위를 가지고 살아가고 있다고 가정해봅시다.
그리고 채용 시즌이 다가와 Person
과 Alien
은 자소서를 쓰기 시작했습니다.
class Resume{
지원자
경력
특이사항
}
여기서 한가지 문제가 발생합니다. Resume
의 지원자 에는 Alien
도 들어갈 수 있어야하고 Person
도 들어갈 수 있어야합니다.
여기서 생각할 수 있는 전통적인 해결책은 상속이 있습니다.
Person
과 Alien
의 공통된 부모 속성을 정의하고, 그 부모속성을 지원자 속성으로 사용하는 것입니다.
그럼 유기체라는 공통 부모를 설정해보죠
class Organism{
int age
}
좋습니다. 이제 Person
도 Alien
도 자식 타입으로 설정할 수 있겠네요.
그런데 인공지능이 발달해서 이제 로봇들도 취업을 하는 시기가 왔습니다. 로봇들도 자소서를 쓰기 시작했습니다.
하지만 로봇은 나이도, 이름도 존재하지 않고 제조사와 일련번호로 구분이 된다고 생각해봅시다.
설계에 엄청난 문제가 생겼습니다. Resume
class 에 지원자 타입은 Organism
이지만, 로봇은 Organism
이 아니기 때문입니다.
여기서 나올 수 있는 해법은 OR
타입(UNION 타입
)입니다.
만약에 Resume
타입을 이렇게 생각하면 어떨까요?
class Resume{
지원자 tpye: Person OR Alien OR Robot
경력
특이사항
}
Person
또는 Alien
또는 Robot
이다이처럼 OR type
을 통한 접근은 문제를 쉽게 만들어줄 때가 있습니다.
서로소 유니온 타입은, 겹치지 않는 타입으로 이루어진 유니온 타입을 의미합니다.
가장 간단한 예시는 Boolean
type 이 있습니다.
Boolean 은 True
이라면 절대 False
일 수 없고, False
라면 절대 True
일 수 없습니다.
서로소 유니온 타입과 같은 말은 Tagged union
sum type
등이 있습니다.
서로소 유니온 타입이 유용한 상황은, 어떤 상태가 서로 겹치지 않는 상태 중 하나
일때 입니다.
함수형 언어에서는 이를 이용한 패턴매칭이라는 활용법이 있습니다(스칼라,Rust등 )
match status {
200 => do(OK),
404 => do(PageNotFound),
403 => do(Frobidden),
_ => notmatchedError,
}
<패턴매칭의 예시>
자바스크립트에서는 언어적으로 지원하지 않지만, 제안 단계로 올라와있습니다 (https://github.com/tc39/proposal-pattern-matching)
if 문을 식으로 변경한것이 3항 연산자라면,
패턴매칭은 switch문을 식으로 변경한것이라 할 수 있습니다.
가령 네트워크 요청에서, status 상태에 따라 서로 다른 처리를 해줘야 할 경우, 이는 서로소유니온 타입이라고 할 수 있습니다.
(200 이면서 404인 상태는 존재하지 않기때문)
그렇기때문에 위와 같이 패턴매칭을 통해 상태에 따라 처리를 해줄 수 있습니다.
패턴매칭을 사용하면 선언적으로 문제를 해결할 수 있습니다.
const { matches } = require('z')
const fibo = (i) => {
return matches(i)(
(x = 0) => 0,
(x = 1) => 1,
(x = Number) => fibo(i - 1) + fibo(i - 2)
);
};
console.log(fibo(10)); //55
n 번째 피보나치수를 구하는 식을 if문 하나 없이 구현할 수 있습니다. 이처럼 문제를 배타적인 상태로 정의한 후, 함수 내부에서 명령형을 제거하고 선언형으로 문제를 해결하는 방법입니다.
패턴매칭을 사용하면, 원하는 데이터 구조를 정하고, 그 안에서 특정한 값을 추출해 특정한 함수로 넘겨줄 수 있습니다.
해당 글의 초반부에서처럼, 객체지향은 이러한 문제를 상속과 다형성으로 해결합니다. 반면 함수형 패러다임은 이를 함수 수준에서 해결합니다.
React 에서도 fetch 상태에 따라 다른 view 를 그리는데 활용할 수 있습니다.
<Fetch url={API_URL}>{
props => case (props) {
when {loading} -> <Loading />
when {error} -> <Error error={error} />
when {data} -> <Page data={data} />
}
}
</Fetch>
위 예시는 아직 제안 단계이기때문에 바로 사용하긴 어렵습니다.
그래서 자바스크립트에서 패턴 매칭을 사용할 수 있는 Daggy
라는 라이브러리가 있습니다.(https://github.com/fantasyland/daggy)
Daggy
의 사용법을 간단히 설명하면 아래와 같습니다
const Item = tagged('Item', ['title'])
const List = taggedSum('List',{
Initial:[],
Success:[Item],
Failure:[]
});
tagged 함수와 taggedSum 함수를 사용하여 타입을 정의합니다.
이제 각각의 타입에 대해 패턴매칭할 함수 객체를 만들어줍니다.
const cataObject = {
Initial:()=> console.log("empty"),
Success:(e)=> console.log(e),
Failure:()=> console.log("ERROR~")
}
cata 라는 함수를 통해 페턴매칭을 실행할 수 있습니다.
let list = List.Initial;
list.cata(cataObject)
// empty 를 출력
list = List.Failure
list.cata(cataObject)
//ERROR 를 출력
list = List.Success([{title:1},{title:2},{title:3}])
list.cata(cataObject)
//0: {title: 1}
//1: {title: 2}
//2: {title: 3}
// 를 출력
즉 list 에 어떤 타입을 넣어주냐에 따라서, 분기처리 없이 원하는 결과를 얻어낼 수 있습니다.
위에서 말했던 리액트 view 를 그리는 예제를 daggy 로 구현해본다면
fetch("something~")
.then(res=>{
setData(List.Success(res));
})
<Container>
{
List.cata({
Initial:()=> <Init/>,
Success:(data)=> <Page data={data} />,
Error:()=> <Error error={error}/>,
Loading:()=> <Loading />
})
}
}
</Container>
이런 형식으로 구현되겠습니다.
만일 if 문으로 구현했다면
if (Initial) {
return ...
}
if(Loading){
return ...
}
if (Error) {
return ...
}
과 같은 형식으로 구현되었을겁니다.
SUM Type 그리고 패턴매칭에 대해 알아보았습니다.
그리고 자바스크립트에서 SUM Type 을 어떻게 사용할 수 있는지, 관련 라이브러리를 사용하여 알아보았습니다.
사실 TypeScript 에서는 서로소 유니온타입까진 아니어도, 유니온 타입을 일상적으로 사용하기때문에 신선한 내용이 아닐 수도 있겠습니다. 그래도 서로소 유니온 타입에 대해 이해할 수 있는 유익한 글이 되었길 바라겠습니다.
오랜만에 글 다시 봤는데, 정말 글을 잘 쓰신 것 같아요 👍