Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 237 additions & 0 deletions Chapter_13/함수형_도구_체이닝.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
### 이번장에서 살필 내용

- 복합적인 쿼리로 데이터를 조회하기 위해 함수형 도구를 조합하는 방법
- 복잡한 반복문을 함수형 도구 체인으로 바꾸는 방법
- 데이터 변환 파이프라인을 직접 만들어 작업을 수행하는 방법

---

### 인트로

계산이 더 복잡해지면 함수형 도구 하나로 작업을 할 수 없다.

따라서, 여러 단계를 하나로 엮은 체인으로 복합적인 계산을 표현하는 방법을 알아보자.

함수형 도구를 조합해서 읽고 쓰기 쉽고 단순한 단계를 유지하며 복잡한 계산을 해결해나갈것이다.

### 체이닝

여러 단계를 하나로 조합하는 것이다.

### 체인을 명확하게 만들기 1 : 단계에 이름 붙이기

각 단계에 이름을 붙이는 것이다.

```jsx
function **biggestPurchasesBestCustomers**(customers) { // 1
    var bestCustomers = filter(customers, function(customer) {
        return customer.purchases.length >= 3;
    });

    var biggestPurchases = map(bestCustomers, function(customer) { // 2
        return maxKey(customer.purchases, {total: 0}, function(purchase) {
            return purchase.total;
        });
    });

    return biggestPurchases;
}
```

위 코드를 각 단계의 고차 함수를 빼내 이름을 붙일 수 있다

```jsx
function biggestPurchasesBestCustomers(customers) {
    var bestCustomers  = **selectBestCustomers**(customers); // 1
    var biggestPurchases = **getBiggestPurchases**(bestCustomers); // 2
    return biggestPurchases;
}

function selectBestCustomers(customers) { // 고차함수에 이름을 붙여 현재 문맥에 추가
    return filter(customers, function(customer) {
        return customer.purchases.length >= 3;
    });
}

function getBiggestPurchases(customers) {
    return map(customers, **getBiggestPurchase**); // 고차함수를 함수로 쉽게 빼냄
}

function getBiggestPurchase(customer) {
    return maxKey(customer.purchases, {total: 0}, function(purchase) {
        return purchase.total;
    });
}
```

아쉬운점은 아직 콜백 함수를 여전히 인라인으로 사용하고 있다는 점이다.

인라인으로 정의된 콜백함수는 재사용할 수 없다.

### 체인을 명확하게 만들기 2: 콜백에 이름 붙이기

콜백을 빼내 이름을 붙이는 방법이다.

```jsx
function biggestPurchasesBestCustomers(customers) {
    var bestCustomers  = filter(customers, **isGoodCustomer**); // 1
    var biggestPurchases = map(bestCustomers, **getBiggestPurchase**); // 2
    return biggestPurchases;
}

function **isGoodCustomer**(customer) {
    return customer.purchases.length >= 3;
}

function **getBiggestPurchase**(customer) {
    return maxKey(customer.purchases, {total: 0}, getPurchaseTotal);
}

function getPurchaseTotal(purchase) {
    return purchase.total;
}
```

콜백을 빼내고 이름을 붙여 재사용할 수 있는 함수로 만들었다.

**selectBestCustomers()함수는 고개 배열로만 쓸 수 있지만 isGoodCustomer() 함수는 고객 하나를 넘겨 쓸 수 있다.**

### 체인을 명확하게 만들기 3 : 두 방법을 비교

두번째 방법이 명확하다.

고차 함수를 그대로 쓰는 첫번째 방법보다 이름을 붙이 두 번째 방법이 재사용하기 더 좋다.

인라인 대신 이름을 붙여 코랩긍ㄹ 사용하면 단계가 중첩되는 것을 방지할 수 있다.

### 스트림 결합

map(), filter(), reducer() 체인을 최적화하는 것이다.

```jsx
// 값 하나에 map() 두 번 사용
var names    = map(customers, getFullName);
var nameLengths = map(names, stringLength);
```

```jsx
// 값 하나에 map() 한 번 사용
var nameLengths = map(customers, function(customer) {
    return stringLength(getFullName(customer));
});
```

오른쪽 코드는 카비지 컬렉션이 필요없다.

### 리팩터링 팁 1: 데이터 만들기 (반복문을 함수형 도구로 만들기)

```jsx
var answer = [];

var window = 5;

for(var i = 0; i < array.length; i++) {
    var sum = 0;
    var count = 0;
    for(var w = 0; w < window; w++) {
        var idx = i + w;
        if(idx < array.length) {
            sum  += array[idx];
            count += 1;
        }
    }
    answer.push(sum/count);
}
```

.slice()를 활용해 리팩토링

```jsx
var answer = [];

var window = 5;


for(var i = 0; i < array.length; i++) {
    var sum  = 0;
    var count = 0;
    var subarray = array.slice(i, i + window);
    for(var w = 0; w < subarray.length; w++) {
        sum  += subarray[w];
        count += 1;
    }
    answer.push(sum/count);
}
```

### 팁 2: 한 번에 전체 배열을 조작하기

```jsx
var answer = [];

var window = 5;

    for(var i = 0; i < array.length; i++) {
      var subarray = array.slice(i, i + window);
      answer.push(average(subarray)); // 안쪽 반복문 전체를 .slice()와 average()를 호출
    }
```

### 팁 3: 작은 단계로 나누기

```jsx
var indices = [];

for(var i = 0; i < array.length; i++) // index가 들어있는 배열을 만든다
    indices.push(i);

var window = 5;

var answer = map(indices, function(i) { // 하위 배열 만들고 평균 계산하는 일 2개나 함
    var subarray = array.slice(i, i + window);
    return average(subarray);
});
```

```jsx
function range(start, end) {
    var ret = [];
    for(var i = start; i < end; i++)
        ret.push(i);
    return ret;
}

var window = 5;

var indices = range(0, array.length); // 인덱스 배열 생성
var windows = map(indices, function(i) { // 하위 배열만들기
    return array.slice(i, i + window);
});
var answer = map(windows, average); // 평균 계산하기
```

처음 절차적인 코드에서 함수형 코드로 변경되었다.

**보너스 팁**

- 조건문을 filter()로 바꾸기
- 유용한 함수로 추출하기, 알려준 3개 말고도 더 좋은 도구를 스스로 찾아봐라
- 개선을 위해 함수형 도구 조합을 실험하기

### 체이닝 디버깅을 위한 팁

- 구체적인 것을 유지하기
- 출력해보기
- 타입을 따라가보기

### 다양한 함수형 도구

1. pluck(), concat(), frequenciesBy() & groupBy()
- [https://medium.com/@cheonmyung0217/underscore-js-pluck-함수-c71319710d67](https://medium.com/@cheonmyung0217/underscore-js-pluck-%ED%95%A8%EC%88%98-c71319710d67)
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/groupBy

1. 도구 찾는 곳
- Lodash
- Laravel(PHP)
- 클로저 표준 라이브러리
- 하스켈