[개발 서적] 함수형 코딩 Grokking Simplicity - 2

박연주·2024년 1월 6일
0

개발 서적

목록 보기
2/3

II. 일급 추상


ch 10. 일급 함수 1

  • 일급함수와 고차함수 - 미리보고 오기
  • 추상화를 잘 할 수있는 리팩터링
    • 암묵적 인자를 드러내기
    • 함수 본문을 콜백으로 바꾸기


리팩터링 1 - 암묵적 인자를 드러내기

  • 함수 이름의 일부가 암묵적 인자로 사용되고 있다면 암묵적 인자를 드러냄으로써 리팩터링 가능
    -> 명시적 인자로 바꾸고 하드 코딩된 값을 새로운 인자로 바꿈
// 함수명 set'Price'ByName의 Price가 암묵적 인자
function setPriceByName(cart, name, price) {
	var item = cart[name];        // 인자
  	var newItem = objectSet(item, 'price', price);
    var newCart = objectSet(cart, name, newItem);
}

cart = setPriceByName(cart, "shoe", 20000)



// 명시적 인자 field를 추가
// 원래 인자는 더 명시적인 이름 value로 바꿈
function setFieldByName(cart, name, field, value) {
	var item = cart[name];
  	var newItem = objectSet(item, field, value);
    var newCart = objectSet(cart, name, newItem);
}

cart = setFieldByName(cart, "shoe", 'price', 20000)



function obejctSet(object, key, value) {
  var copy = Object.assign({}, object);
  copy[key] = value;
  return copy;
}


Object.assign(target, ...sources)

- 매개변수 target
목표 객체. 출처 객체의 속성을 복사해 반영한 후 반환할 객체입니다.

- sources
출처 객체. 목표 객체에 반영하고자 하는 속성들을 갖고 있는 객체들입니다.

// 예시
const obj = { a: 1 };
const copy = Object.assign({}, obj);
console.log(copy); // { a: 1 }

-> 이제 암묵적인 이름은 인자로 넘길 수 있는 값(여기서는 문자열)이 됨
-> 값은 변수나 배열에 담을 수 있고 그래서 일급(frist-class)라 부름



리팩터링 2 - 함수 본문을 콜백으로 바꾸기

  • 코드를 함수로 감싸기
  • 더 일반적인 이름으로 바꾸기
  • 암묵적 인자를 드러내기
  • 함수 추출하기
  • 암묵적 인자를 드러내기
    등의 여러가지 고차 함수를 만드는 방법이 있음
    -> 통틀어서 함수 본문을 콜백으로 바꾸기라는 리팩터링으로 부르겠음
// 원래 코드
try {
    saveUserData(user);
} catch (error) {
    logToSnapErrors(error);
}


// 함수로 빼내기
function withLogging() {
    try {
        saveUserData(user);    // 콜백으로 빼낼 예정
    } catch (error) {
        logToSnapErrors(error);
    }
}

// 콜백으로 빼내기
function withLogging(f) {
    try {
        f();                  // 콜백으로 빼내어 아래에서 본문 전달
    } catch (error) {
        logToSnapErrors(error);
    }
}

withLogging(function() {
    saveUserData(user);
});



ch 11. 일급 함수 2

배열에 대한 카피온 라이ㅌ트 리팩터링

// 원래 코드 - copy on write
function arraySet(array, idx, value) {
  var copy = array.slice();
  copy[idx] = value;
  return copy;
}


// 함수로 빼내기
function arraySet(array, idx, value) {
  return withArrayCopy(array);
}

function withArrayCopy(array) {
  var copy = array.slice();
  copy[idx] = value;      // idx와 value를 위에서 알 수 없어서 동작 x
}


// 콜백 빼내기
function arraySet(array, idx, value) {
  return withArrayCopy(array, function(copy) {
    copy[idx] = value;     // 필요한 부분을 인자로 만들어 전달 
  });
}

function withArrayCopy(array, modify) {
  var copy = array.slice();
  modify(copy);           // 콜백
  return copy;
}


함수를 리턴하는 함수

  • saveUserData(), fetchProduct() 와 같은 로그를 남기지 않는 함수를 로그를 남기도록 변경하기
    -> withLogging으로 일일이 감싸줘야 함 - ch11_p280.js



ch12. 함수형 반복

  • 반복문 대신 함수형 도구를 사용해 배열을 다룸

map()

  • 배열을 받아 하나씩 변환해서 같은 길이의 배열로 만들어주는 함수, 고차 함수로 배열 반복

반복문을 활용하여 다양한 종류의 email 보내기

// Code from chapter 3
function emailsForCustomers(customers, goods, bests) {
  var emails = [];
  for(var i = 0; i < customers.length; i++) {
    var customer = customers[i];
    var email = emailForCustomer(customer, goods, bests);
    emails.push(email);
  }
  return emails;
}

// Using forEach()
function emailsForCustomers(customers, goods, bests) {
  var emails = [];
  forEach(customers, function(customer) {
    var email = emailForCustomer(customer, goods, bests);
    emails.push(email);
  });
  return emails;
}

// forEach()
function forEach(array, f) {
	for (var i = 0; i < array.length; i++) {
    	var item = array[i];
        f(item);
    }
}

반복문 대신 map()을 활용하기

/// Original Code
function emailsForCustomers(customers, goods, bests) {
  var emails = [];
  forEach(customer, function(customer) {
    var email = emailForCustomer(customer, goods, bests);  // 본문
    emails.push(email);
  });
  return emails;
}

function biggestPurchasePerCustomer(customers) {
  var purchases = [];
  forEach(customer, function(customer) {
      var customer = customers[i];
      var purchase = biggestPurchase(customer);           // 본문
      purchases.push(purchase);
  });
  return purchases;
}



/// 본문을 콜백으로, forEach()는 map()으로 빼내 일반적인 함수로 변경
function emailsForCustomers(customers, goods, bests) {
  return map(customers, function(customer) {          // 본문을 콜백으로 전달
    return emailForCustomer(customer, goods, bests);
  });
}

function biggestPurchasePerCustomer(customers) {
  return map(customers, function(customer) {          // 본문을 콜백으로 전달
    return biggestPurchase(customer);
  });
}

function map(array, f) {
  var newArray = [];
  forEach(array, function(element) {
    newArray.push(f(element));  // 콜백 부름
  });
  return newArray;
}

// 순서 : emailsForCustomers() -> map -> forEach() 
//       -> 콜백함수(emailForCustomer) -> return newArray


filter()

  • 원래 배열을 가지고 새로운 배열을 만드는 고차 함수
  • 새로운 배열에 담을 항목과 건너뛸 항목을 결정할 수 있음
/// Original Code
function selectBestCustomers(customers) {
  var newArray = [];
  forEach(customers, function(customer) {
    if(customer.purchases.length >= 3)      // 본문이 표현식
      newArray.push(customer);
  });
  return newArray;
}


/// 본문은 콜백으로, 이외의 부분은 filter()로 빼내기
function selectBestCustomers(customers) {
  return filter(customers, function(customer) {
    return customer.purchases.length >= 3   // 본문
  })

}

function filter(array, f) {
    var newArray = [];
    forEach(array, function(element) {
        if (f(element)) {  // 조건식을 콜백으로 부름
            newArray.push(element);
        }
    });
  return newArray;
}


reduce()

  • 배열을 순회하면서 값을 누적해 가는 함수
/// Original Code
function countAllPurchases(customers) {			// 달라지는 부분
  var total = 0;                                // 초기값
  forEach(customers, function(customer) {
    total = total + customer.purchases.length;  // 본문
  });
  return total;
}


/// 초기값과 본문 외에 reduce()로 빼내기
function countAllPurchases(customers) {
    return reduce(customers, 0, function(total, customer) {   // 초기값과 본문 
        return total + customer.purchases.length;
    });
}

function reduce(array, init, f) {
    var accum = init;
    forEach(array, function(element) {
        accum = f(accum, element);
    });
    return accum;
}
// reduce()를 활용해 배열에서 가장 작은 값 찾기
function min(numbers) {
    return reduce(numbers, Number.MAX_VALUE, function(m, num) {
        if (m < num) return m;
        else return num;
    });
}

ch13. 함수형 도구 체이닝

  • 단계들을 조합해 하나의 쿼리로 만드는 것을 체이닝이라 함
  • 우수 고객의 구매 중 가장 비싼 구매를 알아내기
    1. 우수 고객(3개 이상 구메)을 거름 (filter)
    2. 우수 고객을 가장 비싼 구매로 바꿈(map)
  • 최종 메서드 체인을 명확하게 만들기
    1. 단계에 이름 붙이기 (각 단계의 고차 함수 빼내기)
    2. 콜백에 이름 붙이기 (더욱 명확)
  • 예시 - ch13_p319.js

ch14. 중첩된 데이터에 함수형 도구 사용하기

  • update() 빼내기
/// Original Code
function incrementField(item, field) {
  var value = item[field];
  var newValue = value + 1;
  var newItem = objectSet(item, field, newValue);
  return newItem;
}

/// Extracted update
function incrementField(item, field) {
  return updateField(item, field, function(value) {
  return value + 1;
  });
}

function updateField(item, field, modify) {
  var value = item[field];
  var newValue = modify(value);
  var newItem = objectSet(item, field, newValue);
  return newItem;
}


/// Called update()
function update(object, key, modify) {
  var value = object[key];
  var newValue = modify(value);    // 바뀌는 부분
  var newObject = objectSet(object, key, newValue);
  return newObject;
}
  • 중첩된 데이터에 udpate() 사용하기
    - update3, update4를 사용해보며 재귀함수 이끌어 내보기 - ch14_p369.js

재귀 함수

  • 재귀는 for, while 반복문처러머 무한 반복에 빠질 수 있음
  • 안전한 재귀 사용법
    - 종료 조건 : 재귀가 멈춰야하는 곳에 종료 조건이 있어야 함
    - 재귀 호출 : 재귀 함수는 최소 하나의 재귀 호출이 있어야 함
    - 종료 조건에 다가가기 : 최소 하나 이상의 인자가 점점 줄어들어야 함








Reference

다음장에서 계속

profile
하루에 한 개념씩

0개의 댓글