우석
일급함수 2
콜백 빼내기 리팩터링의 장점
function withArrayCopy(array, modify) {
var copy = array.slice();
modify(array);
return copy;
}
// 활용
function arraySet(array, idx, value) {
return withArrayCopy(array, function(copy) {
copy[idx] = value;
})
}
- withArrayCopy()는 배열과 콜백 메서드를 인자로 받고, 배열에 카피-온-라이트를 적용한 후 콜백 메서드를 수행하고 반환한다.
- 즉, 카피-온-라이트를 적용해주는 메서드이다. 이렇게 구현하면 카피-온-라이트가 필요한 곳에 매번 구현할 필요 없이 withArrayCopy()를 사용하면 된다.
- 또한 카피-온-라이트에 대한 코드를 한 곳에서 관리할 수 있고, 함수의 동작을 최적화하기 편리하다.
콜백 빼내기 리팩터링의 단점
- 하지만 이러한 방식에도 2가지 문제점이 존재한다.
- 어떤 부분에 카피-온-라이트를 적용하는 것을 깜빡할 수 있다.
- 모든 코드에 수동으로 withArrayCopy()를 적용해야 한다.
해결책: 함수 팩토리를 활용하라
function wrapWithArrayCopy(f) {
return function(array, arg1, arg2, arg3) {
var copy = array.slice();
f(array, arg1, arg2, arg3);
return copy;
}
}
var arraySetWithCopy = wrapWithArrayCopy(arraySet);
- 책에서는 함수를 리턴하는 함수 팩토리를 사용하는 방식을 제안한다.
- 함수 팩토리는 함수에 콜백 인자를 추가하는 대신 해당 함수를 새로운 함수로 감싼다.
- 이 경우 카피-온-라이트가 적용된 메서드인지 명확히 알 수 있다 (카피-온-라이트로 감싼 함수를 만들고, 카피-온-라이트 동작에 대한 내용을 메서드 명으로 식별한다.)
- 단, 모든 코드에 적용이 필요한 점은 변하지 않는다.
- 함수 팩토리는 적절하게 사용하면 유용하지만 코드를 복잡하게 만드는 요소이기도 하기 때문에 꼭 필요한 때인지를 고민하며 사용하자.
함수 팩토리에 대한 생각
- 이전에 읽을 때도 의문이 남았지만 다시 읽어도 의문이 생긴다.. 함수 팩토리는 과연 괜찮은 방법일까?
- 함수를 래핑하여 함수 명으로 copy-on-write가 적용되었음을 명시적으로 표현하지만, 이전 문제점 1, 2가 사라지지는 않는다.
- 이점이라면 1번 문제점을 조금이나마 줄일 수 있다는 점? 하지만 장점에 비해 복잡성이 많이 증가하지 않나 싶다..
- 아직은 고차 함수가 익숙하지 않아서 더 그런 것 같기도 하다..
만혁
일급 함수 2
배열에 대한 카피-온-라이트 리팩터링
1. 본문과 앞부분, 뒷부분을 확인하기
function arraySet(array, idx, value) {
const copy = array.slice(); // 앞부분
copy[idx] = value; // 본문
return copy; // 뒷부분
}
function dropLast(array, idx, value) {
const copy = array.slice(); // 앞부분
copy.pop(); // 본문
return copy; // 뒷부분
}
2. 함수 빼내기
// as-is
function arraySet(array, idx, value) {
const copy = array.slice();
copy[idx] = value;
return copy;
}
// to-be
function arraySet(array, idx, value) {
return withArrayCopy(array);
}
function withArrayCopy(array) {
const copy = array.slice();
copy[idx] = value; // 아직 정의되지 않은 변수
return copy;
}
3. 콜백 빼내기
function arraySet(array, idx, value) {
return withArrayCopy(
array,
function(copy) { copy[idx] = value }
);
}
// 카피-온-라이트 원칙을 따르고 재사용할 수 있는 함수
function withArrayCopy(array, modify) {
const copy = array.slice();
modify(copy);
return copy;
}
연습 문제
withArrayCopy()
함수 적용해보기
function push(array, elem) {
return withArrayCopy(
array,
function() { copy.push(elem) }
)
}
function dropLast(array) {
return withArrayCopy(
array,
function() { copy.pop() }
)
}
function dropFirst(array) {
return withArrayCopy(
array,
function() { copy.shift() }
)
}
object 에도 사용 가능한 카피-온-라이트 버전을 만들어라
function withObjectCopy(object, modify) {
const copy = Object.assign({}, object);
modify(copy);
return copy;
}
function objectSet(object, key, value) {
return withObjectCopy(
object,
function(copy) { copy[key] = value }
)
}
function objectDelete(object, key) {
return withObjectCopy(
object,
function(copy) { delete copy[key] }
);
}
tryCatch()
만들어라
tryCatch(sendEmail, logToSnapErrors)
이렇게 쓸 수 있게~~
function tryCatch(f, e) {
try {
return f();
} catch (error) {
return e(error);
}
}
when()
함수를 만들어라
when(hasItem(cart, "shoes"), function() {return setPriceByName(cart, "shoes", 0)})
function when(predicate, f) {
if (predicate) {
return f()
}
}
when()
함수를 IF()
로 이름 바꾸고 else
를 추가해라
function IF(predicate, then, ELSE) {
if (predicate) {
return then();
} else {
return ELSE();
}
}
함수를 리턴하는 함수
원래 코드
try {
saveUserData(user);
} catch (error) {
logToSnapErrors(error);
}
이름을 명확하게 바꿈
try {
saveUserDataNoLogging(user);
} catch (error) {
logToSnapErrors(error);
}
로그를 남기는 함수
function saveUserDataWithLogging(user) { // 앞부분
try {
saveUserDataNoLogging(user); // 본문
} catch (error) { // 뒷부분
logToSnapErrors(error);
}
}
function fetchProductWithLogging(user) { // 앞부분
try {
fetchProductNoLogging(user); // 본문
} catch (error) { // 뒷 부분
logToSnapErrors(error);
}
}
중복 제거
function (arg) {
try {
saveUserDataNoLogging(arg);
} catch (error) {
logToSnapErrors(error);
}
}
function (arg) {
try {
fetchProductNoLogging(arg);
} catch (error) {
logToSnapErrors(error);
}
}
함수 본문을 콜백으로 바꾸기
function wrapLogging(f) {
return function (arg) {
try {
f(arg);
} catch (error) {
logToSnapErrors(error);
}
}
}
const saveUserDataWithLogging = wrapLogging(saveUserDataNoLogging)
saveUserDataWithLogging(user) // 함수 호출
연습 문제
예외가 발생했을 때 에러를 무시하는 함수, 최소 3개의 인자를 갖는 함수에서 쓸 수 있어야함
function wrapIgnoreErrors(f) {
return function(f1, f2, f3) {
try {
return f(f1, f2, f3);
} catch (error) {
return null;
}
}
}
다른 숫자에 어떤 숫자를 더하는 makeAdder()
함수 만들기
// 사용 예시
const increment = makeAdder(1);
increment(10) // > 11
const plus10 = makeAdder(10);
plus10(12) // > 22
function makeAdder(x) {
return function(y) {
return x + y;
}
}
회독
- 여러 함수형 라이브러리를 사용하다보면
try/catch
를 함수로 제공하는 경우가 많은데 - 다시 보니 어떤 용도로 제공하는지 알것같다