3장. 액션과 계산, 데이터의 차이를 알기
액션과 계산, 데이터
-
액션
- 실행 시점과 횟수에 의존한다.
- 부수 효과, 부수 효과가 있는 함수, 순수하지 않은 함수 라고도 불림
-
계산
- 입력으로 출력을 계산한다.
- 순수 함수, 수학 함수 라고도 불림
-
데이터
- 이벤트에 대한 사실
- 일어난 일의 결과를 기록한 것
-
장보기 과정으로 액션, 계산, 데이터 분리해보기
- 액션과 계산, 데이터는 어디에나 적용할 수 있다.
- 액션 안에는 계산과 데이터, 또 다른 액션이 숨어 있을지도 모른다.
- 계산은 더 작은 계산과 데이터로 나누고 연결할 수 있다.
- 데이터는 데이터만 조합할 수 있다.
- 계산은 때로 ‘우리 머릿속에서’ 일어난다.
데이터
- 함수형 프로그래밍의 데이터 불변성
- 카피-온-라이트 : 변경할 때 복사본을 만든다.
- 방어적 복사 : 보관하려고 하는 데이터의 복사본을 만든다.
- 데이터의 장점
- 직렬화 : 직렬화된 데이터는 전송하거나 디스크에 저장했다가 읽기 쉬움
- 동일성 비교 : 데이터 간 비교가 쉬움
- 자유로운 해석 : 여러 가지 방법으로 해석 가능
- 단점
- 해석이 반드시 필요
쿠폰 보내기 서비스에 함수형 사고
적용하기
- 친구 10명을 추천하면 더 좋은 쿠폰을 보내주려고 한다.
- 이메일 DB에는 이메일별로 각 사용자가 추천한 친구 수를 기록한다.
- 쿠폰DB는 각 쿠폰에 ‘bad’, ‘good’, ‘best’과 같은 등급 정보가 있다.
1. 서비스를 구현하는데 필요한 것들을 액션과 계산, 데이터로 분류하기
2. 타임라인 다이어그램으로 그리기
3. 다이어그램을 코드로 구현하기
var subscriber = {
email: "sam@pmail.com",
rec_count: 16,
};
var rank1 = "best";
var rank2 = "good";
function subCouponRank(subscriber) {
if (subscriber.rec_count >= 10) return "best";
else return "good";
}
var coupon = {
code: "10PERCENT",
rank: "bad",
};
function selectCouponsByRank(coupons, rank) {
var ret = [];
for (var c = 0; c < coupons.length; c++) {
var coupon = coupons[c];
if (coupon.rank === rank) ret.push(coupon.code);
}
return ret;
}
var message = {
from: "newsletter@coupondog.co",
to: "sam@pmail.com",
subject: "Your weekly coupons inside",
body: "Here are your coupons ...",
};
function emailForSubscriber(subscriber, goods, bests) {
var rank = subCouponRank(subscriber);
if (rank === "best")
return {
from: "newsletter@coupondog.co",
to: subscriber.email,
subject: "Your best weekly coupons inside",
body: "Here are the best coupons: " + bests.join(", "),
};
// rank === "good"
else
return {
from: "newsletter@coupondog.co",
to: subscriber.email,
subject: "Your good weekly coupons inside",
body: "Here are the good coupons: " + goods.join(", "),
};
}
function emailsForSubscribers(subscribers, goods, bests) {
var emails = [];
for (var s = 0; s < subscribers.length; s++) {
var subscriber = subscribers[s];
var email = emailForSubscriber(subscriber, goods, bests);
emails.push(email);
}
return emails;
}
function sendIssue() {
var coupons = fetchCouponsFromDB();
var goodCoupons = selectCouponsByRank(coupons, "good");
var bestCoupons = selectCouponsByRank(coupons, "best");
var subscribers = fetchSubscribersFromDB();
var emails = emailsForSubscribers(subscribers, goodCoupons, bestCoupons);
for (var e = 0; e < emails.length; e++) {
var email = emails[e];
emailSystem.send(email);
}
}
데이터는 사용하는 데 제약이 많고 액션은 가장 제약이 없다.
⇒ 데이터→계산→액션
순으로 구현하는 것이 함수형 프로그래밍의 일반적인 구현 순서이다.
이미 있는 코드에 함수형 사고 적용하기
function figurePayout(affiliate) {
var owed = affiliate.sales * affiliate.commission;
if (owed > 100)
// don’t send payouts less than $100
sendPayout(affiliate.bank_code, owed);
}
function affiliatePayout(affiliates) {
for (var a = 0; a < affiliates.length; a++) figurePayout(affiliates[a]);
}
function main(affiliates) {
affiliatePayout(affiliates);
}
→ 해당 코드는 함수형 코드라고 하기 어려움.
← 강조 표시한 액션부터 ~~~ 코드 전체로 액션이 퍼져나감
keyPoint
- 액션은 실행 시점이나 횟수에 의존한다. 일반적으로 액션은 외부 세계에 영향을 주거나 받는다.
- 계산은 입력값으로 출력값을 만드는 것이다. 외부 세계에 영향을 주거나 받지 않고 실행 시점이나 횟수에 의존하지 않는다.
- 데이터는 이벤트에 대한 사실이다. 사실은 변하지 않기 때문에 영구적으로 기록할 수 있다.
- 함수형 프로그래머는 액션보다 계산을 좋아하고 계산보다 데이터를 좋아한다.
- 계산은 같은 입력값을 주면 항상 같은 출력값이 나오기 때문에 액션보다 테스트하기 쉽다.
[도전] 알고리즘 문제에 함수형 사고 적용해보기
- 단어들의 길이가 다르면 오름차순으로 정렬
- 단어들의 길이가 같으면 사전순으로 정렬
- 사전의 앞에서 K번째 위치한 단어 출력
// Run by Node.js
const readline = require("readline");
(async () => {
let rl = readline.createInterface({ input: process.stdin });
let input = [];
for await (const line of rl) {
input.push(line);
}
rl.close();
const result = findWord(input);
console.log(result);
process.exit();
})();
// 입력값 분류하기
function classifyInput(input) {
const [N, K] = input[0].split(" ").map(Number);
const words = input.slice(1);
return [N, K, words];
}
// 단어 정렬하기
function sortWords(words) {
words.sort((a, b) =>
a.length == b.length ? a.localeCompare(b) : a.length - b.length
);
return words;
}
// 특정 위치의 단어 출력하기
function outputWord(words, K) {
return words[K - 1];
}
// 단어 찾기
function findWord(input) {
const [N, K, words] = classifyInput(input);
const sortedWords = sortWords(words);
const printedWord = outputWord(sortedWords, K);
return printedWord;
}