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;
}