함수형코딩 스터디 1주차

·2023년 6월 6일
3

스터디

목록 보기
1/5
post-thumbnail

함수형 프로그래밍은 무엇인가요 ?

  • 수학함수를 사용하고 부수효과를 피하는 것이 특징인 프로그래밍 패러다임
  • 부수 효과 없이 순수 함수만 사용하는 프로그래밍 스타일

부수효과

함수가 리턴값 이외에 하는 모든 일 (결과값을 주는 것 외에 하는 행동)
ex:) 메일 보내기, 전역상태 수정하기, 웹요청하기

순수함수
인자에만 의존하고 부수 효과가 없는 함수

문제 1 : 부수효과는 필요합니다.

부수효과는 소프트웨어를 실행하는 이유입니다.

문제 2: 함수형 프로그래밍은 부수 효과를 잘 다룰 수 있습니다.

정의에는 순수 함수만 쓰라는 것 처럼 되어있지만,함수형 프로그래머는 순수하지 않은 함수도 사용합니다. 그리고 순수하지 않은 함수를 잘 다룰 수 있는 기술이 많이 있습니다.

액션과 계산,데이터 구분하기

함수형 프로그래머는 직감적으로 코드를 세 분류로 나눕니다.

  1. 액션
    1. 시점과 횟수에 의존합니다.
  2. 계산
    1. 입력값으로 출력값을 만드는 것입니다.
    2. 언제,어디서 계산해고 결과는 같고 외부에 영향을 주지 않습니다.
  3. 데이터
    1. 정적이고 해석이 필요합니다.

분산시스템 규칙 3가지

  1. 메세지 순서가 바뀔 수 있다.
  2. 메세지는 한 번 이상 도착할 수도 있고 도착하지 않을 수도 있다.
  3. 응답을 받지 못하면 무슨 일이 생겼는지 알 수 없다.

현실에서의 함수형 사고

🍕 피자를 만드는 것을 함수형으로 프로그래밍 한다면?

1. 액션

호출 횟수와 시점에 의존합니다. 고로 오븐이나 배달차 같은 자원과 요리 재료를 사용하는 것은 액션입니다.

  • 반죽 펴기
  • 피자 배달
  • 재료 주문

2. 계산

어떤 것을 결정하거나 계획하는 것은 계산입니다. 계산은 실행해도 다른 곳에 영향을 주지 않습니다.

  • 조리법에 나온 것을 두 배로 만들기
  • 쇼핑 목록 결정

3. 데이터

  • 고객 주문
  • 영수증
  • 조리법

🍕 장보기 과정 절차를 생각해보고 액션,계산,데이터로 나눈다면 ?

장보기과정

  1. 냉장고 확인하기 [액션]
    • 냉장고를 확인하는 시점에 따라 제품이 다릅니다.
  2. 운전해서 상점으로 가기 [액션]
    • 두 번 운전해서 상점에 가면 연료가 두 배로 듭니다.
  3. 필요한 것 구입하기 [액션]
    • 누군가 브로콜리를 구입하면 다 떨어질 수도 있기 때문에 구입 시점이 중요합니다.
  4. 운전해서 집으로 오기 [액션]

1=> 냉장고를 확인하는 일은 시점이 중요하기 때문에 액션입니다.
냉장고가 가지고 있는 제품은 데이터 입니다.

2=> 여기에는 데이터가 숨어있습니다. 상점 위치나 가는 경로는 데이터로 볼 수 있습니다.

장보기에서 배운 것

  1. 액션과 계산,데이터는 어디에나 적용할 수 있습니다.
  2. 액션 안에는 계산과 데이터, 또 다른 액션이 숨어 있을지도 모릅니다.
    1. 단순해 보이는 액션도 또 다른 액션이나 계산,데이터로 나눌 수 있습니다.
  3. 계산은 더 작은 계산과 데이터로 나누고 연결할 수 있습니다.
  4. 데이터는 데이터만 조합할 수 있습니다.
  5. 계산은 때로 우리 머릿속에서 일어납니다.
    1. 장을 보다가 갑자기 무엇을 사야 할지 앉아서 목록을 작성하지 않습니다. 머릿속에서 저절로 생각이 납니다. 이처럼 계산은 머릿속에서 나도 모르게 일어납니다.
    2. 이 사실을 알면 계산을 더 쉽게 찾을 수 있습니다. 어떤 단계에서 무엇인가 결정할 것이 있는지 또는 무엇인가 계획해서 방법을 찾아야 할 것이 있는지 스스로에게 물어보면 됩니다.
    3. 결정과 계획은 계산이 될 가능성이 높습니다.

코드에 함수형 사고 적용하기

쿠폰독의 새로운 마케팅 전략

요구사항

  • 쿠폰독은 사용자를 늘리기 위해 친구 10명을 추천하면 “best”쿠폰을 보냅니다.
  • “good” 쿠폰은 모든 사용자들에게 전달되는 쿠폰입니다.
  • “bad” 쿠폰은 사용하지 않습니다.

이메일 데이터베이스 테이블

eamilres_count
seorim6417@naver.com5
js123@co.kr16

쿠폰 데이터베이스 테이블

couponrank
10PERCENTgood
PROMOTIONbad
IHEARTYOUbest

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

이 단계는 액션입니다. 구독자는 계속 바뀌기 때문에 시점에 따라 가져온 구독자가 다를 수 있습니다.
이 단계는 실행 시점에 의존합니다.

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

이 단계 또한 액션입니다. 쿠폰 데이터는 계속 바뀌기 때문에 시점이 중요합니다.

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

함수형 프로그래머는 처리 과정에 필요한 데이터를 만들기도 합니다.
⇒ 장을 볼 때 돌아다니면서 생각나는 것을 사지 않고 장보기 전에 목록을 만드는 것과 비슷하다고 할 수 있습니다.

4. 이메일 전송하기

이메일 메세지에는 수신자와 보낸 내용이 다 만들어졌기 때문에 목록을 순회하며 보내기만 하면 됩니다. 여기서 중요한 것은 계힉할 것을 미리 계획했다는 것입니다.

어떤 구독자가 어떤 등급의 쿠폰을 받을지 결정하는 함수

function subCouponRank(subscriber){ //입력
if(subscriber.rec_count >= 10) return "best" //계산
else return "good" //출력
}

이 함수는 명확하고 테스트하기 쉬우며 재사용할 수 있습니다.

특정 등급의 쿠폰 목록을 선택하는 함수

function selectCouponByRank (coupons,rank){ //쿠폰목록, 지정할ranck
const ret = []
for(let c =0; c < coupons.length; c++){
  const coupon = coupons[c]
   if(coupon.rank === rank) ret.push(coupon.)
   }
return ret; //배열을 리턴
//출력
}

같은 입력값을 넣었을 때 같은 값이 나오나요? ⭕
함수에 대해 호출 횟수가 영향을 줄까요? ❌
ㄴ 아무리 호출해도 외부에 어떠한 영향도 주지 않습니다.

따라서 이 함수는 계산입니다.

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

  • 구독자에 대한 이메일을 만드는 함수이기 때문에 구독자를 인자로 받아야합니다.
  • 이메일에는 쿠폰 정보가 있어야 하기 때문에 쿠폰 목록도 입력값으로 받아야 합니다.
  • 어떤 쿠폰인지 알 수 없기 때문에 good쿠폰 목록,best쿠폰 목록 전부 입력값으로 받아야 합니다.
  • 결과값은 이메일 데이터입니다.
    인자로 받을 것 : 구독자,쿠폰 목록
function emailForSubscriber(subscriber,goods,bests){
const rank = subCouponRank(subscriber) //return best or good
if(rank ==='best'){
//best에 대한 email처리
}else{
//good에 대한 email처리
}
여기서 의문..

1.  subscriber을 넘기는 것 말고 count만 넘기는건?

1. 쿠폰 종류가 많아지면 인자로 다 넘길텐지?

구독자가 받을 이메일 생성하는 계산을 만들었습니다.
이제 필요한 것은 구독자 목록으로 전체 이메일 목록을 만드는 것입니다. 모든 구독자 목록을 받아와 반복문을 돌아 emailForSubscriber 계산을 이용해 모든 구독자들이 받을 이메일을 계산합니다.

보낼 이메일 목록이 모두 준비되었습니다!

모든 이메일 목록이 준비되었고 이제 이메일 보내기를 수행합니다.이메일 보내기는 액션입니다.
이와같이 데이터를 먼저 구현하고 계산을 구현한 후에 마지막으로 액션을 구현하는 것이 함수형 프로그래밍의 일반적인 구현 순서입니다.

절차 : 데이터구현⇒ 계산⇒ 액션실행

액션에서 계산 빼내기

MegarMart는 온라인 쇼핑몰입니다.

주요 기능중 하나는 쇼핑 중에 장바구니에 담겨 있는 제품의 금액 합계를 볼 수 있는 기능입니다.

1-1

var shoppingCart = []
var shoppingCartTotal = 0; //장바구지 제품과 금액 합계를 담고있는 전역변수

function addItemToCart (name,price){
shoppingCart.push({
name, 
price}) //배열에 추가

cartCartTotal(); // 금액 합계 업데이트
}

function cartCartTotal(){
shoppingCartTotal = 0;
for (let i =0; i<shoppingCart.length ; i++){
const item = shoppingCart[i];
shoppingCartTotal += item.price
}

setCartTotalDom();//금앱 합계 반영하기 위해 dom Update

}

새로운 요구사항

합계 구매가 20달러 이상이면 무료 배송을 해주려고 합니다.

합계가 20달러가 넘는 제품의 구매 버튼 옆에 무료 배송 아이콘을 표시해 주려고 합니다.

Now : 🛒 $15


🏀 $6 ⇒ 이 제품을 장바구니에 담으면 총 구입이 21달러이기 때문에
무료 배송 아이콘 표시를 해줍니다.

🎨 $2 ⇒ 이 제품은 장바구니에 담아도 총 17달러이기 때문에
무료 배송 아이콘을 표시하지 않습니다.

절차적인 방법으로 구현해보기

구매 버튼에 무료 배송 아이콘을 표시하기 위한 함수를 만듭니다.

1-2

function updateShoppingIcon(){
const buyButtons = getBuyButtonsDom();
for(let i =0; i<buyButtons.length ; i++){ //페이지에 있는 모든 구매버튼 가져오기
const item= buyButtons[i].item
if(item.price + shoppingCartTotal >= 20){
무료배송 아이콘 보이기()
}
else{
무료배송 아이콘 숨기기()
}

} //end for
} //end updtaeDom

1-3

function calcCartTotal(){ // 1-1
shoppingCartTotal = 0;
for(let i =0; i<shoppingCart.length ; i++){
const item = shoppingCart[i]
shoppingCartTotal += item.price
}//end for

setCartTotalDom()
updateShoppingIcon() //1-2
}

합계 금액이 바뀔 때 마다 모든 아이콘을 업데이트하기 위해
마지막에 updateShoppingIcon() 를 불러줬습니다.

새로운 요구사항

장바구니의 금액 합계가 바뀔 때마다 세금을 다시 계산해야 합니다

1-4

function updateTaxDom(){
setTaxDom(shoppingCartTotal * 0.10)
}

1-5

function calcCartTotal(){ // 1-3 에서 다시 만들었던 함수입니다.
shoppingCartTotal = 0;
for(let i =0; i<shoppingCart.length ; i++){
const item = shoppingCart[i]
shoppingCartTotal += item.price
}//end for

setCartTotalDom()
updateShoppingIcon() //1-2
updateTaxDom() //1-4 
//페이지에 세금을 업데이트하기 위해 코드를 추가합니다.
}

지금 코드는 비즈니스 규칙을 테스트하기 어렵습니다.

코드가 바뀔 때마다 아래와 같은 테스트를 만들어야 합니다.

  1. 브라우저 설정
  2. 페이지 로드
  3. 장바구니 제품담기 버튼 클릭
  4. 돔업데이트 기다리기
  5. 돔에서 값 가져오기
  6. 가져온 문자열 값을 숫자로 바꾸기
  7. 예상하는 값과 비교

문제 제시

function updateTaxDom(){ //1-4
setTaxDom(shoppingCartTotal * 0.10)
// 1. 결과값을 얻는 방법은 돔에서 값을 가져오는 방법뿐입니다.
// 2. 테스트하기 전에 전역변숫값을 설정해야 합니다.
}

개선을 위한 제안 1

  • DOM업데이트와 비즈니스 규칙 분리되어야 합니다.
  • 전역변수가 없어야 합니다.

재사용하기 쉽게 만들기

결제팀과 배송팀이 코드를 재사용하려고 했지만,다음과 같은 이유로 재사용할 수 없었습니다.

  • 장바구니 정보를 전역변수에서 읽어오고 있지만 결제팀과 배송팀은 데이터베이스에서 장바구니 정보를 읽어야합니다.
  • 결과를 보여주기 위해 DOM을 직접 바꾸고 있지만 결제팀은 영수증을,배송팀은 운송장을 출력해야합니다.
function updateShoppingIcon(){ //1-2
const buyButtons = getBuyButtonsDom();
for(let i =0; i<buyButtons.length ; i++){ 
const item= buyButtons[i].item
if(item.price + shoppingCartTotal >= 20){ //결제팀과 배송팀은 
//이 비즈니스 규칙을 사용하려고 합니다.
//이 함수는 전역변수인 shoppingCartTotal 가 필요합니다.

무료배송 아이콘 보이기() //이 코드는 DOM이 있어야 사용가능합니다.
}
else{
무료배송 아이콘 숨기기() //이 코드는 DOM이 있어야 사용가능합니다.
}
//리턴이 없기 때문에 결과를 받을 방법이 없습니다.
}
} 

개선을 위한 제안 2

  • 전역변수에 의존하지 않아야 합니다.
  • DOM을 사용할 수 있는 곳에서 실행된다고 가정하면 안됩니다.
  • 함수가 결과값을 리턴해야 합니다.

입력과 출력은 명시적이거나 암묵적일 수 있습니다

인자는 명시적인 입력입니다. 리턴값은 명시적인 출력입니다.
전역변수를 읽는 것은 암묵적 입력입니다. 전역변수를 바꾸는 것은 암묵적 출력입니다.

명시적 입력암묵적 입력명시적 출력암묵적 출력
인자인자외 다른 입력리턴값리턴 값 외 다른 출력

함수에 암묵적 입력과 출력이 있으면 액션이 됩니다

함수에서 암묵적 입력과 출력을 없애면 계산이 됩니다.
암묵적 입력은 함수의 인자로 바꾸고, 암묵적 출력은 함수의 리턴값으로 바꾸면 됩니다.

액션에서 계산 빼내기

function calcTotal(){
shoppingTotal = 0
for (let i =0; i<shoppingCart.length ; i++){
const item = shoppingCart[i];
shoppingCartTotal += item.price
 }
}

//1-3
function calcCartTotal(){ 
calcTotal()//위에서 새로 만들어준 함수를 불러준다.

setCartTotalDom()
updateShoppingIcon() //1-2
updateTaxDom() //1-4 
//페이지에 세금을 업데이트하기 위해 코드를 추가합니다.
}

함수를 추출했습니다. 하지만 새 함수는 여전히 액션입니다.

function calcTotal(){
shoppingTotal  = 0 //출력
for (let i =0; i<shoppingCart.length ; i++){ //shoppingCart.length === 입력
const item = shoppingCart[i];
shoppingCartTotal += item.price
//shoppingCartTotal  === 출력
 }
}

shoppingTotal 전역 변수 값을 바꾸는 것은 출력입니다.

shoppingCart 전역변수 값을 읽어오는 것은 입력입니다.

여기에 있는 입력과 출력은 모두 암묵적이기 때문에 명시적인 입출력으로 바꿔야 계산이 됩니다.

암묵적 출력을 없앤코드

function calcTotal(){
let total =0; //지역변수로 변경
for (let i =0; i<shoppingCart.length ; i++){
const item = shoppingCart[i];
shoppingCartTotal += item.price
 }
return total
}//리턴값을 받아 전역변수에 할당

function calcCartTotal(){ 
shoppingCartTotal = calcTotal()//리턴값을 받아 전역변수에 할당

setCartTotalDom()
updateShoppingIcon() 
updateTaxDom() 
}

암묵적 입력을 없앤코드

function calcTotal(cart){ //전역변수대신 인자로 사용
let total =0;
for (let i =0; i<cart.length ; i++){
const item = cart[i];
shoppingCartTotal += item.price
 }
return total
}

function calcCartTotal(){ 
shoppingCartTotal = calcTotal(shoppingCart)//shoppingCart를 인자로 전달

setCartTotalDom()
updateShoppingIcon() 
updateTaxDom() 
}
profile
My Island

0개의 댓글