부수효과
함수가 리턴값 이외에 하는 모든 일 (결과값을 주는 것 외에 하는 행동)
ex:) 메일 보내기, 전역상태 수정하기, 웹요청하기
순수함수
인자에만 의존하고 부수 효과가 없는 함수
부수효과는 소프트웨어를 실행하는 이유입니다.
정의에는 순수 함수만 쓰라는 것 처럼 되어있지만,함수형 프로그래머는 순수하지 않은 함수도 사용합니다. 그리고 순수하지 않은 함수를 잘 다룰 수 있는 기술이 많이 있습니다.
함수형 프로그래머는 직감적으로 코드를 세 분류로 나눕니다.
🍕 피자를 만드는 것을 함수형으로 프로그래밍 한다면?
호출 횟수와 시점에 의존합니다. 고로 오븐이나 배달차 같은 자원과 요리 재료를 사용하는 것은 액션입니다.
어떤 것을 결정하거나 계획하는 것은 계산입니다. 계산은 실행해도 다른 곳에 영향을 주지 않습니다.
🍕 장보기 과정 절차를 생각해보고 액션,계산,데이터로 나눈다면 ?
1=> 냉장고를 확인하는 일은 시점이 중요하기 때문에 액션입니다.
냉장고가 가지고 있는 제품은 데이터 입니다.
2=> 여기에는 데이터가 숨어있습니다. 상점 위치나 가는 경로는 데이터로 볼 수 있습니다.
요구사항
이메일 데이터베이스 테이블
eamil | res_count |
---|---|
seorim6417@naver.com | 5 |
js123@co.kr | 16 |
쿠폰 데이터베이스 테이블
coupon | rank |
---|---|
10PERCENT | good |
PROMOTION | bad |
IHEARTYOU | best |
이 단계는 액션입니다. 구독자는 계속 바뀌기 때문에 시점에 따라 가져온 구독자가 다를 수 있습니다.
이 단계는 실행 시점에 의존합니다.
이 단계 또한 액션입니다. 쿠폰 데이터는 계속 바뀌기 때문에 시점이 중요합니다.
함수형 프로그래머는 처리 과정에 필요한 데이터를 만들기도 합니다.
⇒ 장을 볼 때 돌아다니면서 생각나는 것을 사지 않고 장보기 전에 목록을 만드는 것과 비슷하다고 할 수 있습니다.
이메일 메세지에는 수신자와 보낸 내용이 다 만들어졌기 때문에 목록을 순회하며 보내기만 하면 됩니다. 여기서 중요한 것은 계힉할 것을 미리 계획했다는 것입니다.
어떤 구독자가 어떤 등급의 쿠폰을 받을지 결정하는 함수
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; //배열을 리턴
//출력
}
같은 입력값을 넣었을 때 같은 값이 나오나요? ⭕
함수에 대해 호출 횟수가 영향을 줄까요? ❌
ㄴ 아무리 호출해도 외부에 어떠한 영향도 주지 않습니다.
따라서 이 함수는 계산입니다.
구독자가 받을 이메일을 계획하는 계산
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는 온라인 쇼핑몰입니다.
주요 기능중 하나는 쇼핑 중에 장바구니에 담겨 있는 제품의 금액 합계를 볼 수 있는 기능입니다.
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달러이기 때문에
무료 배송 아이콘을 표시하지 않습니다.
구매 버튼에 무료 배송 아이콘을 표시하기 위한 함수를 만듭니다.
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
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() 를 불러줬습니다.
장바구니의 금액 합계가 바뀔 때마다 세금을 다시 계산해야 합니다
function updateTaxDom(){
setTaxDom(shoppingCartTotal * 0.10)
}
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
//페이지에 세금을 업데이트하기 위해 코드를 추가합니다.
}
코드가 바뀔 때마다 아래와 같은 테스트를 만들어야 합니다.
function updateTaxDom(){ //1-4
setTaxDom(shoppingCartTotal * 0.10)
// 1. 결과값을 얻는 방법은 돔에서 값을 가져오는 방법뿐입니다.
// 2. 테스트하기 전에 전역변숫값을 설정해야 합니다.
}
결제팀과 배송팀이 코드를 재사용하려고 했지만,다음과 같은 이유로 재사용할 수 없었습니다.
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이 있어야 사용가능합니다.
}
//리턴이 없기 때문에 결과를 받을 방법이 없습니다.
}
}
인자는 명시적인 입력입니다. 리턴값은 명시적인 출력입니다.
전역변수를 읽는 것은 암묵적 입력입니다. 전역변수를 바꾸는 것은 암묵적 출력입니다.
명시적 입력 | 암묵적 입력 | 명시적 출력 | 암묵적 출력 |
---|---|---|---|
인자 | 인자외 다른 입력 | 리턴값 | 리턴 값 외 다른 출력 |
함수에서 암묵적 입력과 출력을 없애면 계산이 됩니다.
암묵적 입력은 함수의 인자로 바꾸고, 암묵적 출력은 함수의 리턴값으로 바꾸면 됩니다.
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()
}