조컴퓨터

챕터 3. 액션과 계산, 데이터의 차이를 알기 본문

책읽기/쏙쏙 들어오는 함수형 코딩

챕터 3. 액션과 계산, 데이터의 차이를 알기

챠오위 2022. 8. 7. 00:48

파트 1. 액션과 계산, 데이터

챕터 3. 액션과 계산, 데이터의 차이를 알기

 

챕터 3에서 중점적으로 볼 내용은 ? 

- 액션과 계산, 데이터가 어떻게 다른지

- 문제에 대해 생가갛거나 코드를 작성할 때 또는 코드를 읽을 때 액션과 계산, 데이터를 구분해서 적용

- 액션이 코드 전체로 퍼질 수 있다는 것을 이해

- 이미 있는 코드에서 어떤 부분이 액션인지

 

 

액션과 계산, 데이터

1. 액션

   - 실행 시점과 횟수에 의존

   - 부수 효과(side-effects), 부수 효과가 있는 함수(side-effecting function), 순수하지 않은 함수(impure function)

2. 계산 

   - 입력으로 출력을 계산

   - 순수 함수(pure function), 수학 함수(mathematical function)

3. 데이터

   - 이벤트에 대한 사실

 

모든 개발 과정에서 액션과 계산, 데이터를 구분하는 기술을 적용할 수 있다. 

 

함수형 프로그래머는 더 좋은 코드를 만들기 위해 이미 있는 코드를 액션과 계산, 데이터로 리팩터링하는 방법을 찾을 것이다.

 

 

액션과 계산, 데이터는 어디에나 적용할 수 있다

장보는 과정을 예로 들어 액션과 계산, 데이터를 적용해 보자.

 

장보는 과정

냉장고 확인하기 → 운전해서 상점으로 가기 → 필요한 것 구입하기 → 운전해서 집으로 오기

 

위는 전부 액션인 것 같다. 계산과 데이터에 대해 좀 더 명확히 알아 보자.

 

1. 냉장고 확인하기

- 냉장고를 확인하는 일은 확인하는 시점이 중요하기 때문에 액션이다. 

- 냉장고에 있는 제품은 데이터이다.

 

2. 운전해서 상점으로 가기

- 명확한 액션이다.

- 상점 위치로 가는 루트는 데이터로 볼 수 있다. 하지만 자동차 노선을 그리는 것이 아니기 때문에 분리하지 않는다.

 

3. 필요한 것 구입하기

- 명확히 액션이다.

- 하지만 구입 과정을 여러 단계로 나눌 수 있다. 필요한 것을 구입하기 위해 `필요한 제품`에서 `현재 있는 제품`을 빼내 `장보기 목록`을 만든다.

 

4. 운전해서 집으로 오기

- 더 나누지 않는다.

 

이런 형식으로 계속 나누다 보면 점점 더 복잡해진다고 생각할 수 있다. 하지만 액션에 숨어 있는 다른 액션이나 계산 또는 데이터를 발견하기 위해 나눌 수 있는 만큼 계속 나누는 것이 좋다.

 

 

장보기 과정에서 배운 것

1. 액션과 계산, 데이터는 어디에나 적용할 수 있다.

2. 액션 안에는 계산과 데이터, 또 다른 액션이 숨어 있을지도 모른다.

3. 계산은 더 작은 계산과 데이터로 나누고 연결할 수 있다.

4. 데이터는 데이터만 조합할 수 있다.

5. 계산은 때로 `우리 머릿속에서` 일어난다.

 

 


데이터에 대해 자세히 알아보기

1. 데이터

- 데이터는 이벤트에 대한 사실

 

2. 데이터를 어떻게 구현하는가 ?

자바스크립트에서는 기본 데이터 타입으로 구현한다. 숫자나 문자, 배열, 객체 같은 것이다.

다른 언어에서는 더 정교한 방법으로 데이터를 만들 수 있다. 하스켈은 새로운 데이터 타입을 정의해 도메인을 표현한다.

 

3. 어떻게 데이터에 의미를 담을 수 있는가 ?

데이터 구조로 의미를 담을 수 있다.

 

4. 불변성

함수형 프로그래머는 불변 데이터 구조를 만들기 위해 두 가지 원칙을 사용한다.

- 카피-온-라이트(copy-on-write) : 변경할 때 복사본을 만든다.

- 방어적 복사(defensive copy) : 보관하려고 하는 데이터의 복사본을 만든다.

 

5. 데이터의 장점은 무엇인가 ? 

- 직렬화 : 직렬화된 액션이나 계산은 다른 곳에서 잘 동작할 것이라는 보장이 없다. 하지만 직렬화된 데이터는 전송하거나 디스크에 저장했다가 읽기 쉽다.

- 동일성 비교 : 계산이나 액션은 서로 비교 하기 어렵다. 하지만 데이터는 비교하기 쉽다.

- 자유로운 해석 : 데이터는 여러 가지 방법으로 해석할 수 있다. 예를 들어 접속 로그는 문제 해결을 위해 사용할 수도 있지만, 모니터링을 위해 사용할 수도 있다.

 

6. 데이터의 단점은 무엇인가 ?

- 유연하게 해석할 수 있다는 점은 장점임과 동시에 해석이 반드시 필요하다는 점에 있어서 단점이기도 하다. 해석하지 않은 데이터는 쓸모없는 바이트이다.

 


 

새로 만드는 코드에 함수형 사고 적용하기

어떤 한 쿠폰 서비스에서 쿠폰 데이터베이스에 세 가지 등급 정보를 가지고 있다고 하자.

각 등급은 'bad', 'good', 'best' 에 해당한다. 추천을 많이 한 회원에게 등급이 높은 쿠폰을 주는 식으로 서비스를 이어간다.

 

 

쿠폰 보내는 과정 그려보기

1. 데이터베이스에서 구독자를 가져오기

   - 구독자는 가져오는 시점에 따라 달라지기 때문에 액션이다.

   - 구독자를 데이터베이스에서 가져오면 사용자 목록을 얻을 수 있고, 이것은 데이터이다.

2. 데이터베이스에서 쿠폰 목록 가져오기

   - 쿠폰 목록은 가져오는 시점에 따라 달라지기 때문에 액션이다.

   - 가져온 쿠폰 목록은 데이터이다. 

3. 보내야 할 이메일 목록 만들기

   - 이메일 목록은 보내야 할 이메일을 계획한 결과이다.

   - 이메일 목록을 계획하는 계산은 구독자 목록 데이터와 쿠폰 목록 데이터를 받는다. 그에 대한 결과는 이메일 목록이다. 

   - 이는 계산이다. 계산으로 바꿀 수 있는 액션이 있다면 그렇게 진행함이 좋다. 

4. 이메일 전송하기

   - 이메일 메시지에는 수신자와 보낼 쿠폰 목록이 이미 만들었기 때문에 목록을 순회하면서 보낸다.

 

이를 더 잘게 나눌 수 있다. 액션을 계산으로 더 나눌 수 있다. 그리고 계산으로 나누면 구현이 더 쉽다. 

 

 

쿠폰 보내는 과정 구현하기

데이터베이스에서 가져오는 구독자 데이터 

var subscriber = {

   email = "cho@gmail.com",

   rec_cnt = 12

};

 

쿠폰 등급은 문자열이다

var rank1 = "best";

var rank2 = "good";

 

쿠폰 등급을 결정하는 것은 함수이다.

function subCouponRank(subscriber) = {

   if (subscriber.rec_cnt >= 10) {

      return "best";

   } else {

      return "good";

   }

}; 

 

데이터베이스에서 가져온 쿠폰 데이터

var coupons = {

   code : "10PERCENT",

   rank : "bad"

};

 

특정 등급의 쿠폰 목록을 선택하는 계산은 함수이다.

function selectCouponByRank(coupons, rank) {

   var ret = [];

   for (var c = 0; c < coupons.length; c++) {

      var coupon = coupons[c];

      if (coupon.rank === rank) {

         ret.push(coupon, rank);

      }

      return ret;
   }

};

 

이메일은 그냥 데이터이다.

var message = {

   from : "couponAdmin@coupon.com",

   to : "cho@gmail.com",

   subject : "NEW COUPON",

   body : "~~"

};

 

구독자가 받을 이메일을 계획하는 계산

function emailForSubscriber(subscriber, goods, bests) {

   var rank = subCouponRank(subscriber);

   if (rank === "best") {

      return {

         from : "couponAdmin@coupon.com",

         to : "cho@gmail.com",

         subject : "NEW BEST COUPON",

         body : "Here are the best coupons: " + bests.join(", ")

      };

   } else {

      return {

         from : "couponAdmin@coupon.com",

         to : "cho@gmail.com",

         subject : "NEW GOOD COUPON",

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

   }

}

 

모든 기능을 코드로 구현해 보았다. 데이터를 파악하는 것에서 시작하여 계산과 추가 데이터를 도출했다. 그리고 액션으로 모든 것을 묶었다.

데이터는 사용하는 데 제약이 많고 액션은 가장 제약이 없다. 이와 같이 데이터를 먼저 구현하고 계산을 구현한 후에 마지막으로 액션을 구현하는 것이 함수형 프로그래밍의 일반적인 구현 순서이다.

 

 


계산에 대해 자세히 알아보기

1. 계산

- 실행 시점과 횟수에 관계없이 항상 같은 입력값에 대해 같은 출력값을 돌려준다.

 

2. 계산은 어떻게 구현하는가 ? 

- 계산은 함수로 구현한다.

 

3. 어떻게 계산에 의미를 담을 수 있는가 ? 

- 계산에는 연산을 담을 수 있다. 

- 계산을 언제 사용할지 또는 어떻게 사용할지는 때에 따라 다르다.

 

4. 왜 액션보다 계산이 좋은가 ?

- 테스트하기 쉽다. 계산은 언제 어디서나 원하는만큼 테스트를 실행할 수 있다.

- 기계적인 분석이 쉽다. 

- 계산은 조합하기 좋다. 계산을 조합해 더 큰 계산을 만들 수 있다. 이때 일급(high-order) 계산을 사용한다.

 

5. 계산의 예

- 더하기나 곱하기

- 문자열 합치기

- 쇼핑 계획하기

 

6. 계산을 쓰면서 걱정하지 않아도 되는 것은 ?

- 동시에 실행되는 것

- 과거에 실행되었던 것이나 미래에 실행할 것

- 실행 횟수

 

7. 계산의 단점

- 계산과 액션은 실행하기 전에 어떤 일이 발생할지 알 수 없다는 단점이 있다.

- 코드를 읽으면 예상할 수 있으나 소프트웨어 측면에서 함수는 블랙박스이다.

 


 

이미 있는 코드에 함수형 사고 적용하기

function figurePayout(affiliate) {

   var owed = affiliate.sales * affiliate.commission;

   if (owed > 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);

}

 

 

액션은 코드 전체로 퍼진다

위의 코드는 모두 액션에 해당한다. 함수형 사고를 적용하지 않은 코드일 뿐이다.

 

액션은 사용하기 어렵다. 액션을 부르는 함수가 있다면 그 함수도 액션이 된다. 또 그 함수를 부르는 다른 함수도 역시 액션이 된다. 이런 식으로 작은 액션 하나가 코드 전체로 퍼져 나간다.

 

함수형 프로그래머는 액션을 가능한 사용하지 않으려고 한다. 액션을 쓰는 순간 코드 전체로 퍼져 나가기 때문에 사용할 때 조심해야 한다.

 

 

액션은 다양한 형태로 나타난다

함수 호출

alert("Hello world");

 

메서드 호출

console.log("hello");

 

생성자

new Date()

 

표현식

변수 참조

y

속성 참조

user.first_name

배열 참조

stack[0]

 

상태

값 할당

z = 3;

속성 삭제

delete user.first_name;

 

이것도 모두 액션이다. 호출 시점에 따라 다른 결과를 낼 수 있다. 

 

 


액션에 대해 자세히 알아보기

1. 액션

- 액션은 실행 시점과 횟수에 의존한다.

- 언제 실행되는지 : 순서

- 얼마나 실행되는지 : 반복

 

2. 액션은 어떻게 구현하는가 ? 

- 계산도 함수로 구현하기 때문에 구분하기 쉽지 않다.

 

3. 어떻게 액션에 의미를 담을 수 있는가 ? 

- 액션으로 외부에 영향을 줄 수 있다.

- 어떤 일을 하려는지 아는 것이 중요하다.

 

4. 액션의 예

- 이메일 보내기

- 계좌에서 인출하기

- 전역변숫값 바꾸기

- ajax 요청 보내기

 

5. 액션을 잘 사용하기 위한 방법

- 가능한 액션을 적게 사용한다. 액션 대신에 계산을 사용할 수 있는지 생각한다.

- 액션은 가능한 작게 만든다. 액션에서 액션과 관련 없는 코드는 제거한다.

- 액션이 외부에 영향을 주는 것을 제한할 수 있다. 따라서 내부에 계산과 데이터만 있고 가장 바깥쪽에 액션이 있는 구조가 이상적이다. (어니언 아키텍쳐)

- 액션이 호출 시점에 의존하는 것을 제한한다. 함수형 프로그래머는 액션이 호출 시점과 횟수에 덜 의존하도록 만드는 기술을 알고 있다.

 


 

 

출처 : 쏙쏙 들어오는 함수형 코딩(Grokking Simplicity: Taming complex software with functional thinking)