์๋ฐ์คํฌ๋ฆฝํธ ํจํด ๋งค์นญ์ ๊ฐ๋ ฅํจ์ ์์๋ณด์ธ์. ์ด ํจ์ํ ํ๋ก๊ทธ๋๋ฐ ๊ฐ๋ ์ด ์ด๋ป๊ฒ switch ๋ฌธ์ ๊ฐ์ ํ์ฌ ๋ ๊น๋ํ๊ณ , ์ ์ธ์ ์ด๋ฉฐ, ๊ฒฌ๊ณ ํ ์ฝ๋๋ฅผ ๋ง๋๋์ง ๋ฐฐ์๋ณด์ธ์.
์ฐ์ํจ์ ํ: ์๋ฐ์คํฌ๋ฆฝํธ ํจํด ๋งค์นญ ์ฌ์ธต ๋ถ์
์์ญ ๋
๋์ ์๋ฐ์คํฌ๋ฆฝํธ ๊ฐ๋ฐ์๋ค์ ์กฐ๊ฑด๋ถ ๋ก์ง์ ์ํด ์ต์ํ ๋๊ตฌ ์ธํธ์ ์์กดํด ์์ต๋๋ค: ์ ์ ๊น์ if/else ์ฒด์ธ๊ณผ ๊ณ ์ ์ ์ธ switch ๋ฌธ์
๋๋ค. ์ด๊ฒ๋ค์ ๋ถ๊ธฐ ๋ก์ง์ ์ผ๊พผ์ผ๋ก์ ๊ธฐ๋ฅ์ ์ด๊ณ ์์ธก ๊ฐ๋ฅํฉ๋๋ค. ๊ทธ๋ฌ๋ ์ฐ๋ฆฌ์ ์ ํ๋ฆฌ์ผ์ด์
์ด ๋ณต์กํด์ง๊ณ ํจ์ํ ํ๋ก๊ทธ๋๋ฐ๊ณผ ๊ฐ์ ํจ๋ฌ๋ค์์ ์์ฉํจ์ ๋ฐ๋ผ, ์ด๋ฌํ ๋๊ตฌ๋ค์ ํ๊ณ๋ ์ ์ ๋ ๋ช
๋ฐฑํด์ง๋๋ค. ๊ธด if/else ์ฒด์ธ์ ์ฝ๊ธฐ ์ด๋ ค์์ง ์ ์๊ณ , ๋จ์ํ ๋๋ฑ์ฑ ๊ฒ์ฌ์ ํด์ค๋ฃจ(fall-through)์ ๊ธฐ๋ฌํจ์ ๊ฐ์ง switch ๋ฌธ์ ๋ณต์กํ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ๋ค๋ฃฐ ๋ ์ข
์ข
๋ถ์กฑํจ์ด ๋๋ฌ๋ฉ๋๋ค.
ํจํด ๋งค์นญ์ ๋ฑ์ฅ์ ์ฃผ๋ชฉํ์ธ์. ์ด๊ฒ์ ๋จ์ํ '๊ฐํ๋ switch ๋ฌธ'์ด ์๋๋ผ ํจ๋ฌ๋ค์์ ์ ํ์ ๋๋ค. ํ์ค์ผ(Haskell), ML, ๋ฌ์คํธ(Rust)์ ๊ฐ์ ํจ์ํ ์ธ์ด์์ ์ ๋ํ ํจํด ๋งค์นญ์ ๊ฐ์ ์ผ๋ จ์ ํจํด๊ณผ ๋์กฐํ์ฌ ํ์ธํ๋ ๋ฉ์ปค๋์ฆ์ ๋๋ค. ์ด๋ฅผ ํตํด ๋ณต์กํ ๋ฐ์ดํฐ๋ฅผ ๊ตฌ์กฐ ๋ถํดํ๊ณ , ๊ทธ ํํ๋ฅผ ํ์ธํ๋ฉฐ, ๊ทธ ๊ตฌ์กฐ์ ๊ธฐ๋ฐํ์ฌ ์ฝ๋๋ฅผ ์คํํ ์ ์์ผ๋ฉฐ, ์ด ๋ชจ๋ ๊ฒ์ ํ๋์ ํํ๋ ฅ ์๋ ๊ตฌ์กฐ์ฒด ์์์ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. ์ด๊ฒ์ ๋ช ๋ นํ ๊ฒ์ฌ("๊ฐ์ ์ด๋ป๊ฒ ํ์ธํ ๊ฒ์ธ๊ฐ")์์ ์ ์ธํ ๋งค์นญ("๊ฐ์ด ์ด๋ป๊ฒ ์๊ฒผ๋๊ฐ")์ผ๋ก์ ์ ํ์ ๋๋ค.
์ด ๊ธ์ ์ค๋๋ ์๋ฐ์คํฌ๋ฆฝํธ์์ ํจํด ๋งค์นญ์ ์ดํดํ๊ณ ์ฌ์ฉํ๊ธฐ ์ํ ํฌ๊ด์ ์ธ ๊ฐ์ด๋์ ๋๋ค. ์ฐ๋ฆฌ๋ ๊ทธ ํต์ฌ ๊ฐ๋ , ์ค์ ์ ์ฉ ์ฌ๋ก, ๊ทธ๋ฆฌ๊ณ ๋ค์ดํฐ๋ธ ์ธ์ด ๊ธฐ๋ฅ์ด ๋๊ธฐ ํจ์ฌ ์ ๋ถํฐ ํ๋ก์ ํธ์ ์ด ๊ฐ๋ ฅํ ํจ์ํ ํจํด์ ๋์ ํ ์ ์๋ ๋ฐฉ๋ฒ์ ์์๋ณผ ๊ฒ์ ๋๋ค.
ํจํด ๋งค์นญ์ด๋ ๋ฌด์์ธ๊ฐ? Switch ๋ฌธ์ ๋์ด์
ํต์ฌ์ ์ผ๋ก ํจํด ๋งค์นญ์ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ๋ถํดํ์ฌ ํน์ 'ํจํด'์ด๋ ํํ์ ๋ง๋์ง ํ์ธํ๋ ๊ณผ์ ์ ๋๋ค. ์ผ์นํ๋ ํญ๋ชฉ์ด ๋ฐ๊ฒฌ๋๋ฉด, ์ฐ๊ด๋ ์ฝ๋ ๋ธ๋ก์ ์คํํ ์ ์์ผ๋ฉฐ, ์ข ์ข ๋งค์นญ๋ ๋ฐ์ดํฐ์ ์ผ๋ถ๋ฅผ ๋ก์ปฌ ๋ณ์์ ๋ฐ์ธ๋ฉํ์ฌ ํด๋น ๋ธ๋ก ๋ด์์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ด๋ฅผ ์ ํต์ ์ธ switch ๋ฌธ๊ณผ ๋์กฐํด ๋ด
์๋ค. switch๋ ๋จ์ผ ๊ฐ์ ๋ํ ์๊ฒฉํ ๋๋ฑ์ฑ(===) ๊ฒ์ฌ์ ์ ํ๋ฉ๋๋ค:
function getHttpStatusMessage(status) {
switch (status) {
case 200:
return 'OK';
case 404:
return 'Not Found';
case 500:
return 'Internal Server Error';
default:
return 'Unknown Status';
}
}
์ด ๋ฐฉ๋ฒ์ ๋จ์ํ ์์ ๊ฐ์ ๋ํด์๋ ์๋ฒฝํ๊ฒ ์๋ํฉ๋๋ค. ํ์ง๋ง API ์๋ต๊ณผ ๊ฐ์ด ๋ ๋ณต์กํ ๊ฐ์ฒด๋ฅผ ์ฒ๋ฆฌํ๊ณ ์ถ๋ค๋ฉด ์ด๋จ๊น์?
const response = { status: 'success', data: { user: 'John Doe' } };
// or
const errorResponse = { status: 'error', error: { code: 'E401', message: 'Unauthorized' } };
switch ๋ฌธ์ ์ด๋ฅผ ์ฐ์ํ๊ฒ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. ์์ฑ์ ์กด์ฌ ์ฌ๋ถ์ ๊ทธ ๊ฐ์ ํ์ธํ๋ ์ง์ ๋ถํ if/else ๋ฌธ ์๋ฆฌ์ฆ์ ์์กดํด์ผ ํ ๊ฒ์
๋๋ค. ๋ฐ๋ก ์ด ์ง์ ์์ ํจํด ๋งค์นญ์ด ๋น์ ๋ฐํฉ๋๋ค. ๊ฐ์ฒด์ ์ ์ฒด ๊ตฌ์กฐ๋ฅผ ๊ฒ์ฌํ ์ ์์ต๋๋ค.
ํจํด ๋งค์นญ ์ ๊ทผ ๋ฐฉ์์ ๊ฐ๋ ์ ์ผ๋ก ๋ค์๊ณผ ๊ฐ์ ๊ฒ์ ๋๋ค (๊ฐ์์ ๋ฏธ๋ ๋ฌธ๋ฒ ์ฌ์ฉ):
function handleResponse(response) {
return match (response) {
when { status: 'success', data: d }: `Success! Data received for ${d.user}`,
when { status: 'error', error: e }: `Error ${e.code}: ${e.message}`,
default: 'Invalid response format'
}
}
์ฃผ์ ์ฐจ์ด์ ์ ์ฃผ๋ชฉํด ๋ณด์ธ์:
- ๊ตฌ์กฐ์ ๋งค์นญ: ๋จ์ผ ๊ฐ์ด ์๋ ๊ฐ์ฒด์ ํํ์ ๋ํด ๋งค์นญํฉ๋๋ค.
- ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ: ํจํด ๋ด์์ ์ง์ ์ค์ฒฉ๋ ๊ฐ(`d`์ `e` ๋ฑ)์ ์ถ์ถํฉ๋๋ค.
- ํํ์ ์งํฅ: ์ ์ฒด `match` ๋ธ๋ก์ด ๊ฐ์ ๋ฐํํ๋ ํํ์์ด๋ฏ๋ก, ๊ฐ ๋ถ๊ธฐ์์ ์์ ๋ณ์๋ `return` ๋ฌธ์ด ํ์ ์์ต๋๋ค. ์ด๋ ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์ ํต์ฌ ์์น์ ๋๋ค.
์๋ฐ์คํฌ๋ฆฝํธ์์์ ํจํด ๋งค์นญ ํํฉ
์ ์ธ๊ณ ๊ฐ๋ฐ์๋ค์ ์ํด ๋ช ํํ ์ง๊ณ ๋์ด๊ฐ์ผ ํ ์ ์ด ์์ต๋๋ค: ํจํด ๋งค์นญ์ ์์ง ์๋ฐ์คํฌ๋ฆฝํธ์ ํ์ค ๋ค์ดํฐ๋ธ ๊ธฐ๋ฅ์ด ์๋๋๋ค.
ํ์ฌ ECMAScript ํ์ค์ ์ด๋ฅผ ์ถ๊ฐํ๊ธฐ ์ํ TC39 ์ ์์ด ํ๋ฐํ ์งํ ์ค์ ๋๋ค. ๊ทธ๋ฌ๋ ์ด ๊ธ์ ์ฐ๋ ์์ ์์ ์ด ์ ์์ 1๋จ๊ณ(Stage 1)์ ์์ผ๋ฉฐ, ์ด๋ ์ด๊ธฐ ํ์ ๋จ๊ณ์์ ์๋ฏธํฉ๋๋ค. ๋ชจ๋ ์ฃผ์ ๋ธ๋ผ์ฐ์ ์ Node.js ํ๊ฒฝ์์ ๋ค์ดํฐ๋ธ๋ก ๊ตฌํ๋๋ ๊ฒ์ ๋ณด๋ ค๋ฉด ์๋ง๋ ๋ช ๋ ์ ๋ ๊ฑธ๋ฆด ๊ฒ์ ๋๋ค.
๊ทธ๋ ๋ค๋ฉด ์ค๋๋ ์ด๋ป๊ฒ ์ฌ์ฉํ ์ ์์๊น์? ์ฐ๋ฆฌ๋ ํ๋ฐํ ์๋ฐ์คํฌ๋ฆฝํธ ์ํ๊ณ์ ์์กดํ ์ ์์ต๋๋ค. ํ๋ ์๋ฐ์คํฌ๋ฆฝํธ์ ํ์
์คํฌ๋ฆฝํธ์ ํจํด ๋งค์นญ์ ํ์ ๊ฐ์ ธ๋ค์ฃผ๋ ๋ช๋ช ํ๋ฅญํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ด ๊ฐ๋ฐ๋์์ต๋๋ค. ์ด ๊ธ์ ์์ ์์๋ ์ฃผ๋ก ts-pattern์ ์ฌ์ฉํ ๊ฒ์
๋๋ค. ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ํ์
์ด ์๋ฒฝํ๊ฒ ์ง์๋๊ณ ํํ๋ ฅ์ด ๋ฐ์ด๋๋ฉฐ, ํ์
์คํฌ๋ฆฝํธ์ ์์ ์๋ฐ์คํฌ๋ฆฝํธ ํ๋ก์ ํธ ๋ชจ๋์์ ์ํํ๊ฒ ์๋ํ๋ ์ธ๊ธฐ ์๊ณ ๊ฐ๋ ฅํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์
๋๋ค.
ํจ์ํ ํจํด ๋งค์นญ์ ํต์ฌ ๊ฐ๋
์ด์ ์ฌ๋ฌ๋ถ์ด ๋ง์ฃผํ๊ฒ ๋ ๊ธฐ๋ณธ์ ์ธ ํจํด๋ค์ ์ดํด๋ณด๊ฒ ์ต๋๋ค. ์ฝ๋ ์์ ์๋ ts-pattern์ ์ฌ์ฉํ๊ฒ ์ง๋ง, ์ด ๊ฐ๋
๋ค์ ๋๋ถ๋ถ์ ํจํด ๋งค์นญ ๊ตฌํ์ฒด์์ ๋ณดํธ์ ์ผ๋ก ์ฌ์ฉ๋ฉ๋๋ค.
๋ฆฌํฐ๋ด ํจํด: ๊ฐ์ฅ ๋จ์ํ ๋งค์นญ
์ด๊ฒ์ `switch`์ case์ ์ ์ฌํ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ํํ์ ๋งค์นญ์ ๋๋ค. ๋ฌธ์์ด, ์ซ์, ๋ถ๋ฆฌ์ธ, `null`, `undefined`์ ๊ฐ์ ์์ ๊ฐ๊ณผ ๋งค์นญ๋ฉ๋๋ค.
import { match } from 'ts-pattern';
function getPaymentMethod(method) {
return match(method)
.with('credit_card', () => 'Processing with Credit Card Gateway')
.with('paypal', () => 'Redirecting to PayPal')
.with('crypto', () => 'Processing with Cryptocurrency Wallet')
.otherwise(() => 'Invalid Payment Method');
}
console.log(getPaymentMethod('paypal')); // "Redirecting to PayPal"
console.log(getPaymentMethod('bank_transfer')); // "Invalid Payment Method"
.with(pattern, handler) ๊ตฌ๋ฌธ์ด ํต์ฌ์
๋๋ค. .otherwise() ์ ์ `default` case์ ํด๋นํ๋ฉฐ, ๋งค์น๊ฐ ๋ชจ๋ ๊ฐ๋ฅ์ฑ์ ์ฒ๋ฆฌํ๋๋ก(exhaustiveness) ๋ณด์ฅํ๊ธฐ ์ํด ์ข
์ข
ํ์ํฉ๋๋ค.
๊ตฌ์กฐ ๋ถํด ํจํด: ๊ฐ์ฒด์ ๋ฐฐ์ด ํ๊ธฐ
์ด ์ง์ ์์ ํจํด ๋งค์นญ์ด ์ง์ ์ผ๋ก ์ฐจ๋ณํ๋ฉ๋๋ค. ๊ฐ์ฒด์ ๋ฐฐ์ด์ ํํ ๋ฐ ์์ฑ์ ๋ํด ๋งค์นญํ ์ ์์ต๋๋ค.
๊ฐ์ฒด ๊ตฌ์กฐ ๋ถํด:
์ ํ๋ฆฌ์ผ์ด์ ์์ ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๋ค๊ณ ์์ํด ๋ณด์ธ์. ๊ฐ ์ด๋ฒคํธ๋ `type`๊ณผ `payload`๋ฅผ ๊ฐ์ง ๊ฐ์ฒด์ ๋๋ค.
import { match, P } from 'ts-pattern'; // P๋ ํ๋ ์ด์คํ๋ ๊ฐ์ฒด์
๋๋ค
function handleEvent(event) {
return match(event)
.with({ type: 'USER_LOGIN', payload: { userId: P.select() } }, (userId) => {
console.log(`User ${userId} logged in.`);
// ... ๋ก๊ทธ์ธ ๋ถ์ ํจ๊ณผ ํธ๋ฆฌ๊ฑฐ
})
.with({ type: 'ADD_TO_CART', payload: { productId: P.select('id'), quantity: P.select('qty') } }, ({ id, qty }) => {
console.log(`Added ${qty} of product ${id} to the cart.`);
})
.with({ type: 'PAGE_VIEW' }, () => {
console.log('Page view tracked.');
})
.otherwise(() => {
console.log('Unknown event received.');
});
}
handleEvent({ type: 'USER_LOGIN', payload: { userId: 'u-123', timestamp: 1678886400 } });
handleEvent({ type: 'ADD_TO_CART', payload: { productId: 'prod-abc', quantity: 2 } });
์ด ์์ ์์ P.select()๋ ๊ฐ๋ ฅํ ๋๊ตฌ์
๋๋ค. ์ด๊ฒ์ ํด๋น ์์น์ ์ด๋ค ๊ฐ์ด๋ ๋งค์นญํ๊ณ ๊ทธ๊ฒ์ ๋ฐ์ธ๋ฉํ์ฌ ํธ๋ค๋ฌ ํจ์์์ ์ฌ์ฉํ ์ ์๊ฒ ํ๋ ์์ผ๋์นด๋ ์ญํ ์ ํฉ๋๋ค. ๋ ์ค๋ช
์ ์ธ ํธ๋ค๋ฌ ์๊ทธ๋์ฒ๋ฅผ ์ํด ์ ํ๋ ๊ฐ์ ์ด๋ฆ์ ์ง์ ํ ์๋ ์์ต๋๋ค.
๋ฐฐ์ด ๊ตฌ์กฐ ๋ถํด:
๋ฐฐ์ด์ ๊ตฌ์กฐ์ ๋ํด์๋ ๋งค์นญํ ์ ์๋๋ฐ, ์ด๋ ์ปค๋งจ๋ ๋ผ์ธ ์ธ์๋ฅผ ํ์ฑํ๊ฑฐ๋ ํํ๊ณผ ์ ์ฌํ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃจ๋ ์์ ์ ๋งค์ฐ ์ ์ฉํฉ๋๋ค.
function parseCommand(args) {
return match(args)
.with(['install', P.select()], (pkg) => `Installing package: ${pkg}`)
.with(['delete', P.select(), '--force'], (file) => `Force deleting file: ${file}`)
.with(['list'], () => 'Listing all items...')
.with([], () => 'No command provided. Use --help for options.')
.otherwise((unrecognized) => `Error: Unrecognized command sequence: ${unrecognized.join(' ')}`);
}
console.log(parseCommand(['install', 'react'])); // "Installing package: react"
console.log(parseCommand(['delete', 'temp.log', '--force'])); // "Force deleting file: temp.log"
console.log(parseCommand([])); // "No command provided..."
์์ผ๋์นด๋์ ํ๋ ์ด์คํ๋ ํจํด
์ฐ๋ฆฌ๋ ์ด๋ฏธ ๋ฐ์ธ๋ฉ ํ๋ ์ด์คํ๋์ธ P.select()๋ฅผ ๋ณด์์ต๋๋ค. ts-pattern์ ๋ํ ํน์ ์์น์ ๋งค์นญํด์ผ ํ์ง๋ง ๊ทธ ๊ฐ์๋ ๊ด์ฌ์ด ์์ ๋ ์ฌ์ฉํ๋ ๊ฐ๋จํ ์์ผ๋์นด๋ P._๋ฅผ ์ ๊ณตํฉ๋๋ค.
P._(์์ผ๋์นด๋): ์ด๋ค ๊ฐ์ด๋ ๋งค์นญํ์ง๋ง ๋ฐ์ธ๋ฉํ์ง๋ ์์ต๋๋ค. ๊ฐ์ด ์กด์ฌํด์ผ ํ์ง๋ง ์ฌ์ฉํ์ง ์์ ๋ ์๋๋ค.P.select()(ํ๋ ์ด์คํ๋): ์ด๋ค ๊ฐ์ด๋ ๋งค์นญํ๊ณ ํธ๋ค๋ฌ์์ ์ฌ์ฉํ ์ ์๋๋ก ๋ฐ์ธ๋ฉํฉ๋๋ค.
match(data)
.with(['SUCCESS', P._, P.select()], (message) => `Success with message: ${message}`)
// ์ฌ๊ธฐ์๋ ๋ ๋ฒ์งธ ์์๋ ๋ฌด์ํ๊ณ ์ธ ๋ฒ์งธ ์์๋ฅผ ์บก์ฒํฉ๋๋ค.
.otherwise(() => 'No success message');
๊ฐ๋ ์ : .when()์ผ๋ก ์กฐ๊ฑด๋ถ ๋ก์ง ์ถ๊ฐํ๊ธฐ
๋๋ก๋ ํํ๋ง ๋งค์นญํ๋ ๊ฒ์ผ๋ก๋ ์ถฉ๋ถํ์ง ์์ต๋๋ค. ์ถ๊ฐ์ ์ธ ์กฐ๊ฑด์ ๋ํด์ผ ํ ์๋ ์์ต๋๋ค. ์ด๋ ๊ฐ๋ ์ (guard clauses)์ด ์ฌ์ฉ๋ฉ๋๋ค. ts-pattern์์๋ .when() ๋ฉ์๋๋ P.when() ์ ์ด๋ฅผ ํตํด ์ด๋ค์ง๋๋ค.
์ฃผ๋ฌธ์ ์ฒ๋ฆฌํ๋ค๊ณ ์์ํด ๋ณด์ธ์. ๊ณ ๊ฐ์ ์ฃผ๋ฌธ์ ๋ค๋ฅด๊ฒ ์ฒ๋ฆฌํ๊ณ ์ถ์ ๊ฒ์ ๋๋ค.
function getOrderStatus(order) {
return match(order)
.with({ status: 'shipped', total: P.when(t => t > 1000) }, () => 'High-value order shipped.')
.with({ status: 'shipped' }, () => 'Standard order shipped.')
.with({ status: 'processing', items: P.when(items => items.length === 0) }, () => 'Warning: Processing empty order.')
.with({ status: 'processing' }, () => 'Order is being processed.')
.with({ status: 'cancelled' }, () => 'Order has been cancelled.')
.otherwise(() => 'Unknown order status.');
}
console.log(getOrderStatus({ status: 'shipped', total: 1500 })); // "High-value order shipped."
console.log(getOrderStatus({ status: 'shipped', total: 50 })); // "Standard order shipped."
console.log(getOrderStatus({ status: 'processing', items: [] })); // "Warning: Processing empty order."
๋ ๊ตฌ์ฒด์ ์ธ ํจํด(.when() ๊ฐ๋๊ฐ ์๋)์ด ๋ ์ผ๋ฐ์ ์ธ ํจํด๋ณด๋ค ๋จผ์ ์์ผ ํ๋ค๋ ์ ์ ์ ์ํ์ธ์. ์ฑ๊ณต์ ์ผ๋ก ๋งค์นญ๋๋ ์ฒซ ๋ฒ์งธ ํจํด์ด ์ ํ๋ฉ๋๋ค.
ํ์ ๋ฐ ์ ์ด ํจํด
๋ฐ์ดํฐ ํ์ ์ด๋ ์ฌ์ฉ์ ์ ์ ์ ์ด ํจ์์ ๋ํด์๋ ๋งค์นญํ ์ ์์ด ํจ์ฌ ๋ ํฐ ์ ์ฐ์ฑ์ ์ ๊ณตํฉ๋๋ค.
function describeValue(x) {
return match(x)
.with(P.string, () => 'This is a string.')
.with(P.number, () => 'This is a number.')
.with({ message: P.string }, () => 'This is an error object.')
.with(P.instanceOf(Date), (d) => `This is a Date object for ${d.getFullYear()}.`)
.otherwise(() => 'This is some other type of value.');
}
์ต์ ์น ๊ฐ๋ฐ์์์ ์ค์ ์ฌ์ฉ ์ฌ๋ก
์ด๋ก ๋ ์ข์ง๋ง, ํจํด ๋งค์นญ์ด ์ ์ธ๊ณ ๊ฐ๋ฐ์๋ค์ ์ค์ ๋ฌธ์ ๋ฅผ ์ด๋ป๊ฒ ํด๊ฒฐํ๋์ง ์ดํด๋ด ์๋ค.
๋ณต์กํ API ์๋ต ์ฒ๋ฆฌํ๊ธฐ
์ด๊ฒ์ ๊ณ ์ ์ ์ธ ์ฌ์ฉ ์ฌ๋ก์ ๋๋ค. API๋ ๊ฑฐ์ ๋จ์ผํ ๊ณ ์ ๋ ํํ๋ก ๊ฐ์ ๋ฐํํ์ง ์์ต๋๋ค. ์ฑ๊ณต ๊ฐ์ฒด, ๋ค์ํ ์ค๋ฅ ๊ฐ์ฒด ๋๋ ๋ก๋ฉ ์ํ๋ฅผ ๋ฐํํฉ๋๋ค. ํจํด ๋งค์นญ์ ์ด๋ฅผ ์๋ฆ๋ต๊ฒ ์ ๋ฆฌํด ์ค๋๋ค.
Error: The requested resource was not found. An unexpected error occurred: ${err.message}// ์ด๊ฒ์ด ๋ฐ์ดํฐ ํ์นญ ํ
์ ์ํ๋ผ๊ณ ๊ฐ์ ํด ๋ด
์๋ค
const apiState = { status: 'error', error: { code: 403, message: 'Forbidden' } };
function renderUI(state) {
return match(state)
.with({ status: 'loading' }, () => '
.with({ status: 'success', data: P.select() }, (users) => `${users.map(u => `
`)
.with({ status: 'error', error: { code: 404 } }, () => '
.with({ status: 'error', error: P.select() }, (err) => `
.exhaustive(); // ์ฐ๋ฆฌ ์ํ ํ์
์ ๋ชจ๋ ์ผ์ด์ค๊ฐ ์ฒ๋ฆฌ๋์๋์ง ๋ณด์ฅํฉ๋๋ค
}
// document.body.innerHTML = renderUI(apiState);
์ด๊ฒ์ ์ค์ฒฉ๋ if (state.status === 'success') ํ์ธ๋ณด๋ค ํจ์ฌ ๋ ๊ฐ๋
์ฑ์ด ๋๊ณ ๊ฒฌ๊ณ ํฉ๋๋ค.
ํจ์ํ ์ปดํฌ๋ํธ์์์ ์ํ ๊ด๋ฆฌ (์: React)
Redux์ ๊ฐ์ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ React์ `useReducer` ํ ์ ์ฌ์ฉํ ๋, ๋ค์ํ ์ก์ ํ์ ์ ์ฒ๋ฆฌํ๋ ๋ฆฌ๋์ ํจ์๋ฅผ ์ข ์ข ๊ฐ๊ฒ ๋ฉ๋๋ค. `action.type`์ ๋ํ `switch`๊ฐ ์ผ๋ฐ์ ์ด์ง๋ง, ์ ์ฒด `action` ๊ฐ์ฒด์ ๋ํ ํจํด ๋งค์นญ์ด ๋ ์ฐ์ํฉ๋๋ค.
// ์ด์ : switch ๋ฌธ์ ์ฌ์ฉํ ์ผ๋ฐ์ ์ธ ๋ฆฌ๋์
function classicReducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
case 'SET_VALUE':
return { ...state, count: action.payload };
default:
return state;
}
}
// ์ดํ: ํจํด ๋งค์นญ์ ์ฌ์ฉํ ๋ฆฌ๋์
function patternMatchingReducer(state, action) {
return match(action)
.with({ type: 'INCREMENT' }, () => ({ ...state, count: state.count + 1 }))
.with({ type: 'DECREMENT' }, () => ({ ...state, count: state.count - 1 }))
.with({ type: 'SET_VALUE', payload: P.select() }, (value) => ({ ...state, count: value }))
.otherwise(() => state);
}
ํจํด ๋งค์นญ ๋ฒ์ ์ด ๋ ์ ์ธ์ ์ ๋๋ค. ๋ํ ํน์ ์ก์ ํ์ ์ ๋ํด `action.payload`๊ฐ ์กด์ฌํ์ง ์์ ์ ์๋ ๊ฒฝ์ฐ์ ์ด๋ฅผ ์ ๊ทผํ๋ ๊ฒ๊ณผ ๊ฐ์ ์ผ๋ฐ์ ์ธ ๋ฒ๊ทธ๋ฅผ ๋ฐฉ์งํฉ๋๋ค. ํจํด ์์ฒด๊ฐ `'SET_VALUE'` ์ผ์ด์ค์ ๋ํด `payload`๊ฐ ์กด์ฌํด์ผ ํจ์ ๊ฐ์ ํฉ๋๋ค.
์ ํ ์ํ ๊ธฐ๊ณ(FSM) ๊ตฌํํ๊ธฐ
์ ํ ์ํ ๊ธฐ๊ณ๋ ์ ํํ ์์ ์ํ ์ค ํ๋์ ์์ ์ ์๋ ๊ณ์ฐ ๋ชจ๋ธ์ ๋๋ค. ํจํด ๋งค์นญ์ ์ด๋ฌํ ์ํ ๊ฐ์ ์ ํ์ ์ ์ํ๋ ๋ฐ ์๋ฒฝํ ๋๊ตฌ์ ๋๋ค.
// ์ํ: { status: 'idle' } | { status: 'loading' } | { status: 'success', data: T } | { status: 'error', error: E }
// ์ด๋ฒคํธ: { type: 'FETCH' } | { type: 'RESOLVE', data: T } | { type: 'REJECT', error: E }
function stateMachine(currentState, event) {
return match([currentState, event])
.with([{ status: 'idle' }, { type: 'FETCH' }], () => ({ status: 'loading' }))
.with([{ status: 'loading' }, { type: 'RESOLVE', data: P.select() }], (data) => ({ status: 'success', data }))
.with([{ status: 'loading' }, { type: 'REJECT', error: P.select() }], (error) => ({ status: 'error', error }))
.with([{ status: 'error' }, { type: 'FETCH' }], () => ({ status: 'loading' }))
.otherwise(() => currentState); // ๋ค๋ฅธ ๋ชจ๋ ์กฐํฉ์ ๋ํด์๋ ํ์ฌ ์ํ๋ฅผ ์ ์งํฉ๋๋ค
}
์ด ์ ๊ทผ ๋ฐฉ์์ ์ ํจํ ์ํ ์ ํ์ ๋ช ์์ ์ผ๋ก ๋ง๋ค๊ณ ์ถ๋ก ํ๊ธฐ ์ฝ๊ฒ ํฉ๋๋ค.
์ฝ๋ ํ์ง ๋ฐ ์ ์ง๋ณด์์ฑ์ ์ํ ์ด์
ํจํด ๋งค์นญ์ ์ฑํํ๋ ๊ฒ์ ๋จ์ง ์๋ฆฌํ ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒ ์ด์์ ๋๋ค. ์ ์ฒด ์ํํธ์จ์ด ๊ฐ๋ฐ ์๋ช ์ฃผ๊ธฐ์ ์ค์ง์ ์ธ ์ด์ ์ ์ ๊ณตํฉ๋๋ค.
- ๊ฐ๋ ์ฑ & ์ ์ธ์ ์คํ์ผ: ํจํด ๋งค์นญ์ ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ฌํ๋ ๋ช ๋ นํ ๋จ๊ณ๊ฐ ์๋, ๋ฐ์ดํฐ๊ฐ ์ด๋ป๊ฒ ์๊ฒผ๋์ง ์ค๋ช ํ๋๋ก ๊ฐ์ ํฉ๋๋ค. ์ด๋ ๋ค๋ฅธ ๊ฐ๋ฐ์๋ค์ด ๋ฌธํ์ ๋๋ ์ธ์ด์ ๋ฐฐ๊ฒฝ์ ๊ด๊ณ์์ด ์ฝ๋์ ์๋๋ฅผ ๋ ๋ช ํํ๊ฒ ํ์ ํ๋๋ก ๋์ต๋๋ค.
- ๋ถ๋ณ์ฑ๊ณผ ์์ ํจ์: ํจํด ๋งค์นญ์ ํํ์ ์งํฅ์ ํน์ฑ์ ํจ์ํ ํ๋ก๊ทธ๋๋ฐ ์์น๊ณผ ์๋ฒฝํ๊ฒ ๋ค์ด๋ง์ต๋๋ค. ์ํ๋ฅผ ์ง์ ๋ณ๊ฒฝํ๊ธฐ๋ณด๋ค๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ๋ณํํ๊ณ ์๋ก์ด ๊ฐ์ ๋ฐํํ๋๋ก ์ฅ๋ คํฉ๋๋ค. ์ด๋ ๋ถ์ ํจ๊ณผ๋ฅผ ์ค์ด๊ณ ๋ ์์ธก ๊ฐ๋ฅํ ์ฝ๋๋ก ์ด์ด์ง๋๋ค.
- ์ฒ ์ ์ฑ ๊ฒ์ฌ(Exhaustiveness Checking): ์ด๊ฒ์ ์ ๋ขฐ์ฑ์ ์ํ ๊ฒ์ ์ฒด์ธ์ ์ ๋๋ค. ํ์ ์คํฌ๋ฆฝํธ๋ฅผ ์ฌ์ฉํ ๋, `ts-pattern`๊ณผ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ปดํ์ผ ํ์์ ์ ๋์ธ ํ์ ์ ๋ชจ๋ ๊ฐ๋ฅํ ๋ณํ์ ์ฒ๋ฆฌํ๋์ง ๊ฐ์ ํ ์ ์์ต๋๋ค. ์๋ก์ด ์ํ๋ ์ก์ ํ์ ์ ์ถ๊ฐํ๋ฉด, ๋งค์น ํํ์์ ํด๋น ํธ๋ค๋ฌ๋ฅผ ์ถ๊ฐํ ๋๊น์ง ์ปดํ์ผ๋ฌ๊ฐ ์ค๋ฅ๋ฅผ ๋ฐ์์ํต๋๋ค. ์ด ๊ฐ๋จํ ๊ธฐ๋ฅ์ ์ ์ฒด ๋ฐํ์ ์ค๋ฅ ํด๋์ค๋ฅผ ์ ๊ฑฐํฉ๋๋ค.
- ์ํ ๋ณต์ก๋ ๊ฐ์: ๊น๊ฒ ์ค์ฒฉ๋ `if/else` ๊ตฌ์กฐ๋ฅผ ๋จ์ผํ๊ณ ์ ํ์ ์ด๋ฉฐ ์ฝ๊ธฐ ์ฌ์ด ๋ธ๋ก์ผ๋ก ํํํํฉ๋๋ค. ๋ณต์ก๋๊ฐ ๋ฎ์ ์ฝ๋๋ ํ ์คํธ, ๋๋ฒ๊น ๋ฐ ์ ์ง๋ณด์๊ฐ ๋ ์ฝ์ต๋๋ค.
์ค๋ ๋ฐ๋ก ํจํด ๋งค์นญ ์์ํ๊ธฐ
์๋ํด ๋ณผ ์ค๋น๊ฐ ๋์ จ๋์? ์ฌ๊ธฐ ๊ฐ๋จํ๊ณ ์คํ ๊ฐ๋ฅํ ๊ณํ์ด ์์ต๋๋ค:
- ๋๊ตฌ ์ ํ: ๊ฐ๋ ฅํ ๊ธฐ๋ฅ ์ธํธ์ ๋ฐ์ด๋ ํ์
์คํฌ๋ฆฝํธ ์ง์์ ์ํด
ts-pattern์ ๊ฐ๋ ฅํ ์ถ์ฒํฉ๋๋ค. ์ค๋๋ ์๋ฐ์คํฌ๋ฆฝํธ ์ํ๊ณ์์ ์ต๊ณ ์ ํ์ค์ ๋๋ค. - ์ค์น: ์ ํธํ๋ ํจํค์ง ๊ด๋ฆฌ์๋ฅผ ์ฌ์ฉํ์ฌ ํ๋ก์ ํธ์ ์ถ๊ฐํ์ธ์.
npm install ts-pattern
๋๋yarn add ts-pattern - ์์ ์ฝ๋ ์กฐ๊ฐ ๋ฆฌํฉํ ๋งํ๊ธฐ: ๋ฐฐ์ฐ๋ ๊ฐ์ฅ ์ข์ ๋ฐฉ๋ฒ์ ์ง์ ํด๋ณด๋ ๊ฒ์ ๋๋ค. ์ฝ๋๋ฒ ์ด์ค์์ ๋ณต์กํ `switch` ๋ฌธ์ด๋ ์ง์ ๋ถํ `if/else` ์ฒด์ธ์ ์ฐพ์๋ณด์ธ์. props์ ๋ฐ๋ผ ๋ค๋ฅธ UI๋ฅผ ๋ ๋๋งํ๋ ์ปดํฌ๋ํธ, API ๋ฐ์ดํฐ๋ฅผ ํ์ฑํ๋ ํจ์ ๋๋ ๋ฆฌ๋์๊ฐ ๋ ์ ์์ต๋๋ค. ๊ทธ๊ฒ์ ๋ฆฌํฉํ ๋งํด ๋ณด์ธ์.
์ฑ๋ฅ์ ๋ํ ์ฐธ๊ณ ์ฌํญ
์ผ๋ฐ์ ์ธ ์ง๋ฌธ์ ํจํด ๋งค์นญ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ฑ๋ฅ ์ ํ๋ฅผ ์ด๋ํ๋์ง ์ฌ๋ถ์ ๋๋ค. ๋ต์ '๊ทธ๋ ๋ค'์ด์ง๋ง, ๊ฑฐ์ ํญ์ ๋ฌด์ํ ์ ์๋ ์์ค์ ๋๋ค. ์ด๋ฌํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ ๊ณ ๋๋ก ์ต์ ํ๋์ด ์์ผ๋ฉฐ, ๋๋ถ๋ถ์ ์น ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ค๋ฒํค๋๋ ๋ฏธ๋ฏธํฉ๋๋ค. ๊ฐ๋ฐ์ ์์ฐ์ฑ, ์ฝ๋ ๋ช ํ์ฑ, ๋ฒ๊ทธ ์๋ฐฉ์์ ์ป๋ ์์ฒญ๋ ์ด์ต์ ๋ง์ดํฌ๋ก์ด ๋จ์์ ์ฑ๋ฅ ๋น์ฉ์ ํจ์ฌ ๋ฅ๊ฐํฉ๋๋ค. ์ฃ๋ถ๋ฆฌ ์ต์ ํํ์ง ๋ง์ธ์; ๋ช ํํ๊ณ , ์ ํํ๋ฉฐ, ์ ์ง๋ณด์ ๊ฐ๋ฅํ ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒ์ ์ฐ์ ์ํ์ธ์.
๋ฏธ๋: ECMAScript์ ๋ค์ดํฐ๋ธ ํจํด ๋งค์นญ
์ธ๊ธํ๋ฏ์ด, TC39 ์์ํ๋ ํจํด ๋งค์นญ์ ๋ค์ดํฐ๋ธ ๊ธฐ๋ฅ์ผ๋ก ์ถ๊ฐํ๋ ์์ ์ ํ๊ณ ์์ต๋๋ค. ๋ฌธ๋ฒ์ ์์ง ๋ ผ์ ์ค์ด์ง๋ง, ์๋ง ๋ค์๊ณผ ๊ฐ์ ๋ชจ์ต์ผ ๊ฒ์ ๋๋ค:
// ์ ์ฌ์ ์ธ ๋ฏธ๋ ๋ฌธ๋ฒ!
let httpMessage = match (response) {
when { status: 200, body: b } -> `Success with body: ${b}`,
when { status: 404 } -> `Not Found`,
when { status: 5.. } -> `Server Error`,
else -> `Other HTTP response`
};
์ค๋๋ ts-pattern๊ณผ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๊ฐ๋
๊ณผ ํจํด์ ๋ฐฐ์์ผ๋ก์จ, ์ฌ๋ฌ๋ถ์ ํ์ฌ ํ๋ก์ ํธ๋ฅผ ๊ฐ์ ํ๋ ๊ฒ๋ฟ๋ง ์๋๋ผ ์๋ฐ์คํฌ๋ฆฝํธ ์ธ์ด์ ๋ฏธ๋๋ฅผ ์ค๋นํ๋ ๊ฒ์
๋๋ค. ์ฌ๋ฌ๋ถ์ด ๊ตฌ์ถํ๋ ์ ์ ๋ชจ๋ธ์ ์ด๋ฌํ ๊ธฐ๋ฅ์ด ๋ค์ดํฐ๋ธ๊ฐ ๋์์ ๋ ์ง์ ์ ์ผ๋ก ๋ณํ๋ ๊ฒ์
๋๋ค.
๊ฒฐ๋ก : ์๋ฐ์คํฌ๋ฆฝํธ ์กฐ๊ฑด๋ฌธ์ ์ํ ํจ๋ฌ๋ค์ ์ ํ
ํจํด ๋งค์นญ์ switch ๋ฌธ์ ์ํ ๋ฌธ๋ฒ์ ์คํ(syntactic sugar) ๊ทธ ์ด์์
๋๋ค. ์ด๊ฒ์ ์๋ฐ์คํฌ๋ฆฝํธ์์ ์กฐ๊ฑด๋ถ ๋ก์ง์ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ ์์ด ๋ณด๋ค ์ ์ธ์ ์ด๊ณ , ๊ฒฌ๊ณ ํ๋ฉฐ, ๊ธฐ๋ฅ์ ์ธ ์คํ์ผ๋ก์ ๊ทผ๋ณธ์ ์ธ ์ ํ์ ๋ํ๋
๋๋ค. ๋ฐ์ดํฐ์ ํํ์ ๋ํด ์๊ฐํ๋๋ก ์ฅ๋ คํ๋ฉฐ, ์ด๋ ๋ ์ฐ์ํ ๋ฟ๋ง ์๋๋ผ ์๊ฐ์ด ์ง๋จ์ ๋ฐ๋ผ ๋ฒ๊ทธ์ ๋ ๊ฐํ๊ณ ์ ์ง๋ณด์ํ๊ธฐ ์ฌ์ด ์ฝ๋๋ก ์ด์ด์ง๋๋ค.
์ ์ธ๊ณ ๊ฐ๋ฐํ์๊ฒ ํจํด ๋งค์นญ์ ์ฑํํ๋ ๊ฒ์ ๋ ์ผ๊ด๋๊ณ ํํ๋ ฅ ์๋ ์ฝ๋๋ฒ ์ด์ค๋ก ์ด์ด์ง ์ ์์ต๋๋ค. ์ด๋ ์ฐ๋ฆฌ์ ์ ํต์ ์ธ ๋๊ตฌ๋ค์ ๋จ์ํ ๊ฒ์ฌ๋ฅผ ์ด์ํ์ฌ ๋ณต์กํ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํ ๊ณตํต ์ธ์ด๋ฅผ ์ ๊ณตํฉ๋๋ค. ๋ค์ ํ๋ก์ ํธ์์ ์ด๋ฅผ ํ์ํด ๋ณด์๊ธฐ๋ฅผ ๊ถ์ฅํฉ๋๋ค. ์๊ฒ ์์ํ์ฌ ๋ณต์กํ ํจ์๋ฅผ ๋ฆฌํฉํ ๋งํ๊ณ , ๊ทธ๊ฒ์ด ์ฝ๋์ ๊ฐ์ ธ๋ค์ฃผ๋ ๋ช ํ์ฑ๊ณผ ํ์ ๊ฒฝํํด ๋ณด์๊ธธ ๋ฐ๋๋๋ค.