쏙쏙 들어오는 함수형 코딩 CH10

Yunes·2023년 10월 9일
0
post-thumbnail

바쁜 현대인을 위한 요약

ch10 에서는 일급 값, 일급 함수, 고차 함수에 대해 배울 수 있다.

Ch10 요약

일급 값은 변수에 저장할 수 있고 인자로 전달하거나 함수의 리턴값으로 사용할 수 있다.

일급이 아닌 기능은 함수로 감싸 일급으로 만들 수 있다.

고차함수는 다른 함수에 인자로 넘기거나 리턴값으로 받을 수 있는 함수다. 고차 함수를 통해 다양한 동작을 추상화할 수 있다.

함수 이름의 암묵적 인자는 암묵적 인자를 드러내기 리팩토링을 통해 제거할 수 있다.

암묵적 인자 드러내기는 함수 이름 대신 일급 값인 인자로 바꾸는 리팩토링을 말한다.

동작을 추상화하기 위해 본문을 콜백으로 바꾸기 리팩토링을 사용할 수 있다.

리펙토링 기법

암묵적 인자를 드러내기

  1. 함수 이름에 있는 암묵적 인자를 확인
  2. 명시적인 인자 추가
  3. 함수 본문에 하드 코딩된 값을 새로운 인자로 바꾼다.
  4. 함수를 호출하는 곳을 고친다.

함수 이름에 있는 암묵적 인자 냄새의 특징
1. 구현이 비슷하다.
2. 함수 이름에 다른 부분이 함수에서 사용된다.

냄새가 나는 코드

function setPriceByName(cart, name, price) {
  var item = cart[name];
  var newItem = objectSet(item, 'price', price);
  var newCart = objectSet(cart, name, newItem);
  return newCart;
}

function setShippingByName(cart, name, ship) {
  var item = cart[name];
  var newItem = objectSet(item, 'shipping', ship);
  var newCart = objectSet(cart, name, newItem);
  return newCart;
}

function setQuantityByName(cart, name, quant) {
  var item = cart[name];
  var newItem = objectSet(item, 'quantity', quant);
  var newCart = objectSet(cart, name, newItem);
  return newCart;
}

function setTaxByName(cart, name, tax) {
  var item = cart[name];
  var newItem = objectSet(item, 'tax', tax);
  var newCart = objectSet(cart, name, newItem);
  return newCart;
}

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

리팩토링 이후

/// Before

function setPriceByName(cart, name, price) {
  var item = cart[name];
  var newItem = objectSet(item, 'price', price);
  var newCart = objectSet(cart, name, newItem);
  return newCart;
}

cart = setPriceByName(cart, "shoe", 13);
cart = setQuantityByName(cart, "shoe", 3);
cart = setShippingByName(cart, "shoe", 0);
cart = setTaxByName(cart, "shoe", 2.34);

/// After

function setFieldByName(cart, name, field, value) {
  var item = cart[name];
  var newItem = objectSet(item, field, value);
  var newCart = objectSet(cart, name, newItem);
  return newCart;
}

cart = setFieldByName(cart, "shoe", 'price', 13);
cart = setFieldByName(cart, "shoe", 'quantity', 3);
cart = setFieldByName(cart, "shoe", 'shipping', 0);
cart = setFieldByName(cart, "shoe", 'tax', 2.34);

본문을 콜백으로 바꾸기 replace body with callback

  1. 본문과 본문의 앞부분과 뒷부분을 구분한다.
  2. 전체를 함수로 빼낸다.
  3. 본문 부분을 빼낸 함수의 인자로 전달한 함수로 바꾼다.

본문을 try/catch 로 감싸지 않고 함수로 감싼 이유는 코드를 바로 실행해서는 안되기 때문이며 감싼 함수를 호출하기 전까지 실행되지 않는다.

이 리팩토링은 중복을 제거할 수 있다.

리팩토링 이전의 코드

// Page 249

/// Preparing and eating

for(var i = 0; i < foods.length; i++) {
  var food = foods[i];
  cook(food);
  eat(food);
}

/// Washing up

for(var i = 0; i < dishes.length; i++) {
  var dish = dishes[i];
  wash(dish);
  dry(dish);
  putAway(dish);
}

/// Give them names

function cookAndEatFoods() {
  for(var i = 0; i < foods.length; i++) {
    var food = foods[i];
    cook(food);
    eat(food);
  }
}

cookAndEatFoods();

function cleanDishes() {
  for(var i = 0; i < dishes.length; i++) {
    var dish = dishes[i];
    wash(dish);
    dry(dish);
    putAway(dish);
  }
}

cleanDishes();

// Page 250

/// call both "item"

function cookAndEatFoods() {
  for(var i = 0; i < foods.length; i++) {
    var item = foods[i];
    cook(item);
    eat(item);
  }
}

cookAndEatFoods();

function cleanDishes() {
  for(var i = 0; i < dishes.length; i++) {
    var item = dishes[i];
    wash(item);
    dry(item);
    putAway(item);
  }
}

cleanDishes();

/// change name to generic

function cookAndEatArray(array) {
  for(var i = 0; i < array.length; i++) {
    var item = array[i];
    cook(item);
    eat(item);
  }
}

cookAndEatFoods(foods);

function cleanArray(array) {
  for(var i = 0; i < array.length; i++) {
    var item = array[i];
    wash(item);
    dry(item);
    putAway(item);
  }
}

cleanDishes(dishes);

// Page 251

/// extract out function

function cookAndEatArray(array) {
  for(var i = 0; i < array.length; i++) {
    var item = array[i];
    cookAndEat(item);
  }
}

function cookAndEat(food) {
  cook(food);
  eat(food);
}

cookAndEatFoods(foods);

function cleanArray(array) {
  for(var i = 0; i < array.length; i++) {
    var item = array[i];
    clean(item);
  }
}

function clean(dish) {
  wash(dish);
  dry(dish);
  putAway(dish);
}

cleanDishes(dishes);

// Page 252

/// rename to something generic

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

function cookAndEat(food) {
  cook(food);
  eat(food);
}

cookAndEatFoods(foods, cookAndEat);

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

function clean(dish) {
  wash(dish);
  dry(dish);
  putAway(dish);
}

cleanDishes(dishes, clean);

// Page 253

/// rename operateOnArray() to forEach()

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

function cookAndEat(food) {
  cook(food);
  eat(food);
}

forEach(foods, cookAndEat);

function clean(dish) {
  wash(dish);
  dry(dish);
  putAway(dish);
}

forEach(dishes, clean);

// Page 254

/// Using anonymous functions and forEach()

forEach(foods, function(food) {
  cook(food);
  eat(food);
});

forEach(dishes, function(dish) {
  wash(dish);
  dry(dish);
  putAway(dish);
});

본문을 콜백으로 바꾸기 리팩토링을 적용한 예시 코드

// Page 255

try {
  saveUserData(user);
} catch (error) {
  logToSnapErrors(error);
}

// Page 256

try {
  fetchProduct(productId);
} catch (error) {
  logToSnapErrors(error);
}

// Page 257

/// After function extraction

function withLogging() {
  try {
    saveUserData(user);
  } catch (error) {
    logToSnapErrors(error);
  }
}

withLogging();

/// After extracting callback

function withLogging(f) {
  try {
    f();
  } catch (error) {
    logToSnapErrors(error);
  }
}

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

자바스크립트에서 일급이 아닌 것

  1. 수식 연산자
  2. 반복문
  3. 조건문
  4. try/catch 블록

일급으로 할 수 있는 것

  1. 변수에 할당
  2. 함수의 인자로 넘기기
  3. 함수의 리턴값으로 받기
  4. 배열이나 객체에 담기

함수를 정의하는 세가지 방법

전역으로 정의하기

함수를 전역으로 정의하고 이름을 붙여 나중에 붙인 이름으로 프로그램 어디서나 쓸 수 있다.

function saveCurrentUserData() {
	saveUserData(user);
}

withLogging(saveCurrentUserData);

지역적으로 정의하기

함수를 지역 범위 내에서 정의하고 이름을 붙여 사용한다. 범위 바깥에서는 쓸 수 없다. 지역적으로 쓰고 싶고 이름이 필요할 때 유용하다.


function someFunction() {
	var saveCurrentUserData = function() {
		saveUserData(user);
	}
	withLogging(saveCurrentUserData);
}

인라인으로 정의하기

함수를 사용하는 곳에서 바로 정의하니 이름이 없어 익명함수 anonymous function 이라고 부른다. 한 번만 쓰는 짧은 함수에 쓰면 좋다.

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

책갈피

일급 first-class 은 인자로 전달할 수 있다는 말입니다. 그리고 고차 higher-order 라는 말은 함수가 다른 함수를 인자로 받을 수 있다는 말입니다. 일급 함수가 없다면 고차 함수를 만들 수 없습니다. - 252p

고차 함수의 좋은 점은 코드를 추상화할 수 있다는 점이다. - 258p

함수를 정의할 때 변수에 저장해 이름을 붙이거나 배열이나 객체 같은 자료구조에 함수를 보관할 수도 있다. 또는 그냥 그대로 전달할 수도 있다. 이는 일급이기 때문에 할 수 있는 일이다. - 263p

함수 호출의 경우 선택적으로 호출하거나 나중에 호출하거나 새로운 문맥 안에서 호출될 수 있다. - 263p

이미지 출처 : https://product.kyobobook.co.kr/detail/S000001952246

⭐️ 왜 함수에 일반 데이터값으로 전달하지 않고 함수를 전달하나요? - 264p

함수로 감싸서 전달하지 않고 데이터로서 본문을 일급으로 전달하면 코드가 특정 문맥에서 실행되도록 할 수 있기 때문에 try/catch 라는 문맥에서 실행되도록 할 수 있다.

용어

코드의 냄새 - 더 큰 문제를 가져올 수 있는 코드

일급 값 first-class value - 언어에 있는 다른 값처럼 쓸 수 있다.

고차 함수 highter-order funciton - 인자로 함수를 받거나 리턴 값으로 함수를 리턴할 수 있는 함수

데이터 지향 data orientation - 이벤트와 엔티티에 대한 사실을 표현하기 위해 일반 데이터 구조를 사용하는 프로그래밍 형식

정적 타입 statically typed - 컴파일할 때 타입을 검사하는 언어

동적 타입 dynamically typed - 런타임에 타입을 확인하는 언어

콜백 callback - 인자로 전달하는 함수, 핸들러 함수라고도 부른다.

인라인 함수 inline function - 쓰는 곳에서 바로 정의하는 함수를 말한다. 예를 들어 인자 목록에서 바로 정의하는 함수가 인라인 함수다.

익명함수 anonymous function - 이름이 없는 함수

profile
미래의 나를 만들어나가는 한 개발자의 블로그입니다.

0개의 댓글