본문 바로가기

Web[웹]/ES6+ 함수형 언어

[ES6+: 응용] 명령형에서 함수형으로 변환시키기

 

#1 명령형 코드를 함수형으로 변환시키기

 

우리가 이때까지 배웠던 for, if, while 문과 같은 명령형 문을 앞에서 배웠던 함수형으로 간략하게 변환시켜보자.
먼저,  '홀수를 n개 더하는' 명령형 코드를 보자.

 

function f1(limit, list){
    let acc = 0;
    for(const a of list){
        if(a % 2){
            const b = a * a;
            acc += b;
            if (--limit == 0) break;
        }
    }
    console.log(acc); // 35
}

f1(3, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);

 

위 코드는 "리스트를 받아와서 홀수인 값만 뽑아서 거기에 제곱을 하는데, 딱 3개만 뽑고 그 뽑은 값들을 더하는 것"이다. 이 내용을 명령형 코드로 짰다가, 기존에 있던 조건을 간단하게 바꿀려고 했을때, 그 바꾸는 방법은 간단하지도 않으면서 복잡할 것이다. 하지만, 이를 함수형 코드로 짠다면, 간단하면서도 코드가 간결해져서 보기에도 편할 것이다.

 

이제 위 명령형 코드를 함수형 코드로  하나씩 바꿔보자.

 

 

[1] 명령형코드를 함수형코드 바꾸기

 

1. if를 filter로 바꾸기

 

앞에서 배웠던 L.filter를 if문을 빼고 넣으면 같은 동작을 한다.

 

function f1(limit, list){
    let acc = 0;
    for(const a of L.filter(a => a % 2, list)){
            const b = a * a;
            acc += b;
            if (--limit == 0) break;
    }
    console.log(acc); // 35
}

f1(3, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);

 

 

2. 값 변화 후 변수 할당을 map으로 바꾸기

 

L.map을 선언해서 "const b = a * a;" 코드 대체하기

 

function f1(limit, list){
   let acc = 0;
   for(const a of L.map(a => a * a, L.filter(a => a % 2, list))){
      acc += a;
      if (--limit == 0) break;
   }
    console.log(acc); // 35
}

f1(3, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);

 

 

3. break를 take로 바꾸기

 

L.take를 통해 바꿔 볼 것인데, take의 동작을 간단하게 예로 보자.

 

var it = L.take(2, [1, 2, 3]);
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: undefined, done: true}

 

위와 같이 take는 앞의 매개변수 숫자만큼 리스트에 값을 뽑는 역할을 하는데 이를 이용해서 break 문을 대체해볼 것이다.

 

function f1(limit, list){
   let acc = 0;
   for(const a of L.take(limit, L.map(a => a * a, L.filter(a => a % 2, list)))){
      acc += a;
   }
   console.log(acc); // 35
}

 

 

 

4. 축약 및 합산을 reduce로 적용하기

 

어떤 리스트들을 어떤 하나의 값이나, 새로운 값으로 축약하는 함수가 reduce이다. 그래서 reduce를 통해 값을 합산하여 나타낼 것이다.

 

function f1(limit, list){
   console.log( // 35
      _.reduce((acc, a) => acc + a,
         L.take(limit,
            L.map(a => a * a,
               L.filter(a => a % 2, list)))));
}

f1(3, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 

 

위에서 쓴 reduce는 앞에서  배웠던 것 처럼 따로 함수를 선언해서 사용하면 더 보기 간편하다.

 

const add = (a, b) => a + b;

function f1(limit, list){
   console.log( // 35
      _.reduce(add,
         L.take(limit,
            L.map(a => a * a,
               L.filter(a => a % 2, list)))));
}

f1(3, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);

 

이처럼 함수형으로 코드를 짜보면, 기존 명령형 보다 사람이 더 읽기 쉬운 코드로 표현이 된 걸 볼 수 있을 것이다. 이를 앞에서 배운 go를 통해 좀더 편하게 만들고 이전 코드와 비교해보자.

 

/* 명령형 코드 */
function f1(limit, list){
    let acc = 0;
    for(const a of list){
        if(a % 2){
            const b = a * a;
            acc += b;
            if (--limit == 0) break;
        }
    }
    console.log(acc); // 35
}

f1(3, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);

------------------------------------------------

/* 함수형 코드 */
function f1(limit, list){
   _.go(list,
      L.filter(a => a % 2),
      L.map(a => a * a),
      L.take(limit),
      _.reduce(add),
      console.log); // 35
}

f1(3, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);

 

앞서 사용한 명령형 코드보다 훨씬 보기 편하고, 수정하기도 쉬워진 것을 볼 수 있다.

 

 


 

[2] 명령형코드를 함수형코드 바꾸기

 

5. while을 range로 바꾸기

 

먼저, 아래와 같은 명령형 코드가 있다고 보자.

 

function f2(end){
    let i = 0;
    while(i < end){
        console.log(i); // 0 1 2 3 4 5 6 7 8 9
        ++i;
    }
}

f2(10);

 

위 코드 역시 L.range를 통해 똑같은 역할을 하는 코드를 만들 수 있다.

 

function f2(end){
   _.each(console.log, L.range(end)); // 0 1 2 3 4 5 6 7 8 9
}

f2(10);

 

만약 위 코드에서 홀수만 출력해야한다고 한다면 명령형은 다음과 같이 구현할 수 있다.

 

function f2(end){
   let i = 0;
   while(i < end){
      if(i % 2){
         console.log(i); // 1 3 5 7 9
      }
      ++i;
   }
}

f2(10);

 

또, 명령형에서는 이를 if문을 쓰지않고 좀 더 영리하게 표현 할 수 있다.

 

function f2(end){
   let i = 1;
   while(i < end){
      console.log(i); // 1 3 5 7 9
      i += 2;
   }
}

f2(10);

 

함수형 프로그래밍에서도 한 번 작성해보자.

 

function f3(end){
   _.each(console.log, L.range(1, end, 2)); // 1 3 5 7 9
}

f3(10);

 

함수형 프로그래밍에서도 if와 같은 역할을 하는 filter를 쓰지않고 코드를 작성해보았는데, 이처럼 명령형 뿐만 아니라 함수형에서도 유사하게 구현할 수 있는 것을 알아보았다.

 

 


함수형 프로그래밍으로 별 만들기

 

함수형 프로그래밍을 통해 별 만들기 소스를 단계적으로 작성해보자.

 

_.go(
   _.range(1, 6),
   _.map(_.range),
   console.log);

 

▲위 처럼 벌써 계단식으로 코드가 유사하게 나왔음을 볼 수 있다.

 

 

_.go(
   _.range(1, 6),
   _.map(_.range),
   _.map(_.map(_ => '*')),
   console.log);

▲해당 인자를 '*'로 대체해준다.

 

 

_.go(
   _.range(1, 6),
   _.map(_.range),
   _.map(_.map(_ => '*')),
   _.map(_.reduce((a, b) => `${a}${b}`)),
   console.log);

▲reduce를 통해 합쳐준다.

 

 

_.go(
   _.range(1, 6),
   _.map(_.range),
   _.map(_.map(_ => '*')),
   _.map(_.reduce((a, b) => `${a}${b}`)),
   _.reduce((a, b) => `${a}\n${b}`),
   console.log);

▲마지막 역시 reduce를 통해 값을 정리해준다.

 

 

_.go(
   L.range(1, 6),
   L.map(L.range),
   L.map(L.map(_ => '*')),
   L.map(_.reduce((a, b) => `${a}${b}`)),
   _.reduce((a, b) => `${a}\n${b}`),
   console.log);

▲ 그리고 마지막으로 코드를 지연성있게 정리해준다.

 

 

위에서 결과로 나온 코드는 아래처럼도 표현이 가능하다.

 

_.go(
   L.range(1, 6),
   L.map(s => go(
      L.range(s),
      L.map(_ => '*'),
      _.reduce((a, b) => `${a}${b}`)
   )),
   _.reduce((a, b) => `${a}\n${b}`),
   console.log);

 

그리고 좀 더 간결하게 만들기 위해 join 함수를 선언해서 줄여보자.

 

const join = sep => _.reduce((a, b) => `${a}${sep}${b}`);

_.go(
   L.range(1, 6),
   L.map(L.range),
   L.map(L.map(_ => '*')),
   L.map(join('')),
   join('\n'),
   console.log);

 


 

함수형 프로그래밍으로 구구단 코드 만들기

 

구구단 코드 역시도 앞에서 배웠던 것과 마찬가지로 다음과 같이 쉽게 표현이 가능하다.

 

_.go(
   L.range(2, 10), // 2~9단
   L.map(a => _.go(
       L.range(1, 10),
       L.map(b => `${a}x${b}=${a*b}`),
       join('\n')
   )),
   join('\n\n'),
   console.log);