ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Function
    FP 2020. 7. 12. 02:04
    반응형

    출처: https://commons.wikimedia.org/wiki/File:Function_machine2.svg

     

    정의

    함수 란?

    1. () 연산자를 적용하여 평가할 수 있는 모든 호출 가능 표현식
    2. 호출시 사용 가능한 결과값을 반환

    자, void 를 반환하는 함수를 우리는 앞으로 함수로 쳐주지 않기로 한다.

    그럼 뭐라고 부르지?

    일급 시민

    Javascript에서는 함수가 객체이므로 값으로 다룰 수 있어서 일급 또는 일급 시민 이라고 부른다.
    보통 프로그래밍에서 일급이라고 함은, 적용 가능한 모든 연산을 수행할 수 있는 것들을 일급 이라고 부른다.

    (어렵다...?)

    그럼 그러한 연산은 뭐가 있을까?

    1. 값을 할당하거나
    2. 함수의 인자로 전달하거나
    3. 함수의 반환값으로 사용하거나

    보통 위와 같은 케이스를 말한다.

    그럼 Javascript에서 이러한 연산을 어떻게 수행하나 한번 보자.

     

    먼저 값을 할당하거나

    const add = function(a, b) {
      return a + b;
    };
    
    // Or
    // const add = (a, b) => a + b;
    
    add(1, 2);    // 3

     

    함수의 인자로 전달하거나

    function calculator(fn, a, b) {
      return fn(a, b);
    }
    
    calculator(add, 1, 2);    // 3

     

    함수의 반환값으로 사용하거나

    function highOrderFn(fn) {
      return function(a, b) {
        return fn(a, b);
      };
    }
    
    const join = highOrderFn(add);
    join('hello', 'world');    // helloworld

     

    음, 일급 칭호를 받을 만 하군.

     

    고차함수

    바로 위 예제 코드 중 calculatorhighOrderFn 함수를 정의했는데 바로 이게 고차함수(High-order function) 이다.
    즉, 함수를 인수로 전달받거나 반환받을 수 있다면 그게 바로 고차함수다.
    고차함수, 고계함수 둘 다 같은 의미다.

    이 고차함수를 사용하면 과연 이득이 뭘까?
    바로 함수를 전달하고, 반환받는 방식을 통해 굉장히 유연한 모듈성을 갖춘 코드를 작성할 수 있게 된다.

    역시 예제로 보는게 제일 좋겠다. 명령형 방식과 한번 비교해보자.

     

    고객이 우리에게 요구사항을 전달했다.

    미국 거주자 명단을 출력해주세요.

    우리가 가지고 있는 데이터는 아래와 같다.

    class Person {
      constructor(name, country) {
        this.name = name;
        this.country = country;
      }
    }
    
    const people = [
      new Person('huna', 'Korea'),
      new Person('Martin Fowler', 'UK'),
      new Person('James Arthur Gosling', 'Canada'),
      new Person('Douglas Crockford', 'USA'),
      new Person('Yukihiro Matsumoto', 'Japan'),
      new Person('Ryan Dahl', 'USA')
    ];

    명령형 방식을 먼저 보면

    function printPeopleInTheUs(people) {
      for (let i = 0; i < people.length; i++) {
        const thisPerson = people[i];
        if (thisPerson.country === 'USA') {
          console.log(thisPerson);
        }
      }
    }
    
    printPeopleInTheUs(people);
    // Person { name: 'Douglas Crockford', country: 'USA' }
    // Person { name: 'Ryan Dahl', country: 'USA' }

    루프를 돌면서 값들을 하나씩 뽑아 USA 데이터를 출력하면 끝~

    이것도 나쁘지 않은데?

    그렇다. 나쁘지 않다. 이대로 고객에게 전달해주자.

    음, 생각이 바꼇어요. 다른 나라 거주자도 볼 수 있으면 좋겠어요.

     

    자, 이제 고차함수를 통해서 좀 더 유연하게 바꿔보자.

    고차함수를 사용할 수 있게 요구사항을 바꾼 고객에게 박수를 🤬

    function printPeople(people, action) {
      for (let i = 0; i < people.length; i++) {
        action(people[i]);
      }
    }
    
    function action(person) {
      if (person.country === 'USA') {
        console.log(person);
      }
    }
    
    printPeople(people, action);
    // Person { name: 'Douglas Crockford', country: 'USA' }
    // Person { name: 'Ryan Dahl', country: 'USA' }

    고객의 요구사항에 맞게 action 함수를 인자로 변경해서 호출하면 된다.
    근데 이정도로 바꾸려고 고차함수를 쓴다면 좀 허접하지 않는가?
    일단 고객에게 보내보자.

     

    잠시 후...

    저희는 콘솔 말고 파일에도 저장이 가능하면 좋겠어요

    좀 더... 야근을 해보자...

    function printPeople(people, selector, printer) {
      people.forEach(person => selector(person) && printer(person));
    }
    
    const inUs = person => person.country === 'USA';
    
    printPeople(people, inUs, console.log);
    // Person { name: 'Douglas Crockford', country: 'USA' }
    // Person { name: 'Ryan Dahl', country: 'USA' }

    이제 printPeople 함수에 데이터, 조건, 출력 순서로 인자를 전달받아 처리할 수 있도록 개선이 되었다.
    고차함수를 통해 대충 함수 호출부분만 봐도 어떤 일을 하는지 짐작이 가지 않는가?

    다행히 고객도 만족스럽다고 연락이 왔다.

    하지만 우리는 끝을 모르는 개발자.
    리팩토링을 할 시간이다.

    이번 리팩토링에는 커링(currying) 이라는 FP의 꽃 기능을 소개하려고 한다.

    자, 놀라지 마시라.

    const curry = (fn, arity = fn.length, ...args) =>
      arity <= args.length ? fn(...args) : curry.bind(null, fn, arity, ...args);
    
    const inCountry = curry((country, person) => person.country === country);
    
    people.filter(inCountry('USA')).map(console.log)

    결과는 처음과 좀 다르지만 (Array.prototype.map() 함수는 (value, index, array) 를 인자로 받는다)
    마지막 함수를 보면 체이닝을 통해 함수들을 아주 왕성하게 주고받는 코드를 볼 수 있을 것이다.

    또한 go 라는 함수를 통해 함수 합성으로 구현도 가능하다.
    이 함수는 유인동 님의 강의에서 감명깊게 본 코드이다.
    단, go 를 사용하기 위해선 Ramda와 같은 currying이 된 함수형 라이브러리가 필요하다.

    const go = (v, ...fn) => fn.reduce((a, f) => f(a), v);
    
    go(
      people,
      R.filter(inCountry('USA')),
      R.map(console.log)
    );

    curry, go 함수에 대해서는 나중에 따로 포스팅을 한번 할 예정이니 그때까지 천천히 코드를 음미해보길 바란다.




    출처: 함수형 자바스크립트

    반응형

    'FP' 카테고리의 다른 글

    How to make the Object as immutable in JS  (0) 2020.07.09
    FP vs OOP  (0) 2020.07.08
    Initial commit - FP  (0) 2020.07.07

    댓글

Designed by Tistory.