만두의 부트캠프 🤔
  • 1. [JavaScript] 고차함수
    2023년 11월 03일 16시 36분 01초에 업로드 된 글입니다.
    작성자: 만두33

     

     

     

    # 일급 객체 first-class citizen

     

    자바스크립트에서 함수는 일급 객체(First-Class Object)입니다. 

     

    이것은 함수가 다음과 같은 특징을 가지는 것을 의미합니다:

     

     


    1. 변수에 할당할 수 있습니다: 함수는 변수에 할당할 수 있으며, 이 변수를 통해 함수를 참조하거나 호출할 수 있습니다.

    const greet = function(name) {
      console.log(`Hello, ${name}!`);
    };
    
    greet("Alice"); // 함수 호출

      

     

     주의) 변수에 함수를 할당할 때 함수 표현식은 변수에 할당한 다음 사용할 수 있다.

    * 함수 표현식은 할당 전에 사용할 수 없습니다.
     * square(7); // --> ReferenceError: Can't find variable: square
     */
    
    const square = function (num) {
      return num * num;
    };
    
    // 변수 square에는 함수가 할당되어 있으므로 (일급 객체), 함수 호출 연산자 '()'를 사용할 수 있습니다.
    output = square(7);
    console.log(output); // --> 49

     

     

     

     

    2. 함수를 다른 함수의 인자로 전달할 수 있습니다: 함수는 인자로 다른 함수를 받을 수 있습니다.

        이를 통해 함수를 추상화하고 모듈화할 수 있습니다.

    function processArray(arr, callback) {
      for (const item of arr) {
        callback(item);
      }
    }
    
    function printItem(item) {
      console.log(item);
    }
    
    const numbers = [1, 2, 3, 4, 5];
    processArray(numbers, printItem); // 다른 함수를 인자로 전달

     

     

     

     

    3. 함수를 다른 함수에서 반환할 수 있습니다: 함수는 다른 함수 내에서 생성되고 반환될 수 있습니다. 

       이를 통해 클로저(closure)와 같은 기능을 구현할 수 있습니다.

    function multiplier(factor) {
      return function(x) {
        return x * factor;
      };
    }
    
    const double = multiplier(2);
    console.log(double(5)); // 10

     

     

     

    4. 함수를 객체의 프로퍼티로 사용할 수 있습니다: 함수를 객체의 메서드로 사용할 수 있고, 객체의 프로퍼티로 저장할 수 있습니다.

       * 프로퍼티(property)? 객체(object) 내에서 키(key)와 값(value)의 쌍으로 구성된 구성 요소를 가리킵니다.

       객체는 프로퍼티의 집합이며, 프로퍼티는 객체의 특성이나 속성을 정의합니다.

    const person = {
      name: "Alice",
      sayHello: function() {
        console.log(`Hello, my name is ${this.name}.`);
      }
    };
    
    person.sayHello();

     

     

    함수가 일급 객체로 취급되는 것은 자바스크립트의 함수형 프로그래밍 패러다임과 관련된 다양한 기능을 지원하며, 

    더 유연한 코드를 작성하는 데 도움이 됩니다.

     

     

     


     

     

    # 고차함수 higher order function

     

     

    고차 함수(higher order function)는 함수를 전달인자(argument)로 받을 수 있고, 함수를 리턴할 수 있는 함수입니다.

    이때 다른 함수(caller)의 전달인자(argument)로 전달되는 함수를 콜백 함수(callback function)라고 합니다.

    콜백 함수를 전달받은 고차 함수(caller)는, 함수 내부에서 이 콜백 함수를 호출(invoke)할 수 있고, 

    조건에 따라 콜백 함수의 실행 여부를 결정할 수도 있습니다. 아예 호출하지 않을 수도 있고, 여러 번 실행할 수도 있습니다.

    '함수를 리턴하는 함수'는 모양새가 특이한 만큼, 부르는 용어가 따로 있습니다. 

    '함수를 리턴하는 함수'를 고안해 낸 논리학자 하스켈 커리(Haskell Curry)의 이름을 따, 커링 함수라고 합니다. 

     

     다른 함수를 매개변수로 받을 수 있는 함수: 고차함수는 다른 함수를 인자로 받을 수 있습니다. 이것을 함수의 매개변수로 사용하는 것을 통해 다른 함수를 동적으로 조작하거나 확장할 수 있습니다.

    다른 함수를 반환하는 함수: 고차함수는 다른 함수를 반환할 수 있습니다. 이렇게 반환된 함수는 클로저(closure)로서, 자신을 둘러싼 환경의 상태를 기억하고 이를 나중에 사용할 수 있습니다.

    고차함수는 함수형 프로그래밍의 핵심 개념 중 하나이며, 함수를 값처럼 다루고 조작하는 데 사용됩니다. 이를 통해 코드를 더 모듈화하고 재사용성을 높일 수 있습니다. 고차함수는 다양한 함수형 프로그래밍 작업, 예를 들어 매핑, 필터링, 리듀싱 등을 수행하는 데 유용합니다.

     

    고차함수는 코드를 간결하게 작성하고 유지보수를 쉽게 만드는 데 도움을 줍니다. 함수형 프로그래밍 패러다임을 따르는 언어에서는 고차함수가 중요한 역할을 합니다.

     

     

     

     


    # 내장 고차 함수

     

     

    내장 고차 함수는 JavaScript 배열에 내장되어 있는 고차 함수로, 

    배열의 요소를 반복하면서 처리할 수 있는 다양한 메서드를 제공합니다. 

    이러한 함수는 고차 함수로 분류됩니다. 

     

    내장 고차 함수의 특징

    • 함수를 인자로 받을 수 있습니다.
    • 함수를 반환할 수 있습니다.
    • 배열 및 컬렉션 데이터 구조에서 사용되며, 다른 함수를 인자로 받아 데이터 처리 및 조작에 사용합니다.

     

    내장 고차 함수에는 주로 배열을 다루는데 사용되는 함수들이 포함됩니다. 

    주요 내장 고차 함수에는 다음과 같은 것들이 있습니다:
    map(): 배열의 각 요소에 대해 주어진 함수를 호출하고, 그 결과를 새로운 배열로 반환합니다.
    filter(): 주어진 조건에 맞는 요소만으로 이루어진 새로운 배열을 반환합니다.
    reduce(): 배열의 모든 요소에 대해 순차적으로 주어진 함수를 적용하여 하나의 값으로 축소합니다.

    forEach(), sort(), some(), every(), find() ....

     

     


     

    # filter

    filter 함수는 배열을 순회하면서 각 요소에 대해 주어진 함수(콜백 함수)를 호출하고, 

    이 콜백 함수가 true를 반환하는 요소들로 이루어진 새로운 배열을 생성하여 반환합니다.

     

    배열의 각 요소가 특정 논리(함수)에 따르면, 사실(true)일 때 따로 분류합니다(filter).

     

     

    const newArray = array.filter(callback(element[, index[, array]])[, thisArg]);

     

    callback: 각 요소에 대해 실행할 함수로, 요소를 첫 번째 매개변수로 받습니다.
    element: 현재 처리 중인 배열 요소.
    index (선택 사항): 현재 처리 중인 요소의 인덱스.
    array (선택 사항): filter를 호출한 배열.
    thisArg (선택 사항): 콜백 함수 내부에서 사용할 this의 값.
    filter 메소드는 callback 함수에서 반환 값이 true인 요소만 모아 새로운 배열로 반환합니다. callback 함수가 true를 반환하면 해당 요소가 유지되고, false를 반환하면 해당 요소는 걸러집니다.

     

    예시코드)

    const numbers = [1, 2, 3, 4, 5, 6];
    const evenNumbers = numbers.filter(function (number) {
      return number % 2 === 0;
    });
    // evenNumbers에는 [2, 4, 6]이 저장됩니다.

    이렇게하면 배열을 특정 조건에 따라 필터링하여 새로운 배열을 만들 수 있습니다.

     

     


    # map

     

    map은 JavaScript의 배열 내장 메소드 중 하나로, 

    배열의 각 요소에 대해 주어진 함수를 호출하고 그 함수가 반환하는 결과로 이루어진 새로운 배열을 생성합니다. 

    map은 기존 배열을 변경하지 않고 변형된 배열을 반환하는데 사용됩니다.

     

    배열의 각 요소가 특정 논리(함수)에 의해 다른 요소로 지정(map) 됩니다.

     

     

    map 메소드의 구문은 다음과 같습니다:

    const newArray = array.map(callback(currentValue[, index[, array]])[, thisArg]);

     

    callback: 각 요소에 대해 실행할 함수로, 현재 처리 중인 요소를 첫 번째 매개변수로 받습니다.
    currentValue: 현재 처리 중인 배열 요소.
    index (선택 사항): 현재 처리 중인 요소의 인덱스.
    array (선택 사항): map를 호출한 배열.
    thisArg (선택 사항): 콜백 함수 내부에서 사용할 this의 값.


    map 메소드는 배열의 모든 요소를 callback 함수에 전달하고, 각 요소에 대해 함수를 실행합니다. 

    이 함수는 변형된 요소를 반환하고, map 메소드는 이러한 반환 값을 새로운 배열에 모아 반환합니다.

     

     

    예시코드)

    const numbers = [1, 2, 3, 4, 5];
    const squaredNumbers = numbers.map(function (number) {
      return number * number;
    });
    // squaredNumbers에는 [1, 4, 9, 16, 25]이 저장됩니다.

     

    이렇게하면 배열의 각 요소를 변형하고 새로운 배열을 생성할 수 있습니다.

     


    # reduce

     

    reduce는 JavaScript의 배열 내장 메소드 중 하나로, 

    배열 요소를 하나로 줄이거나 병합하는 데 사용됩니다. 

    주로 배열의 각 요소를 순회하면서 누적 값을 계산하고 최종 결과를 반환하는 데 활용됩니다.

     

    배열의 각 요소를 특정 방법(함수)에 따라 원하는 하나의 형태로 응축합니다. (reduction)

     

     

    reduce 메소드의 구문은 다음과 같습니다:

    const result = array.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue]);

     

     

    callback: 누적 연산을 수행하는 함수로, accumulator와 currentValue라는 두 매개변수를 받습니다. 

    accumulator는 누적된 값, currentValue는 배열의 현재 요소입니다.

    accumulator: 누적된 결과 값.

    currentValue: 배열에서 현재 처리 중인 요소.

    index (선택 사항): 현재 처리 중인 요소의 인덱스.

    array (선택 사항): reduce를 호출한 배열.

    initialValue (선택 사항): 최초의 accumulator 값으로 사용할 초기값.

     

     

    reduce 메소드는 배열의 각 요소를 왼쪽에서 오른쪽으로 순회하며 callback 함수를 호출하고, 

    결과 값을 accumulator에 누적합니다. 

    초기값 (initialValue)을 제공하면 이 값부터 시작하고,

    초기값이 없으면 첫 번째 배열 요소부터 시작합니다. 

    누적된 값이 최종 결과가 되며 reduce 메소드는 이 값을 반환합니다.



    예제코드)

    const numbers = [1, 2, 3, 4, 5];
    const sum = numbers.reduce(function (accumulator, currentValue) {
      return accumulator + currentValue;
    }, 0);
    // sum에는 15가 저장됩니다.

     

    reduce를 사용하면 배열의 요소를 조작하고, 필요한 계산을 수행하여 최종 결과를 얻을 수 있습니다.

    reduce는 여러 데이터를, 하나의 데이터로 응축(reduce)할 때 사용합니다.

     

     


    # reduce의 색다른 사용법

    # 배열을 문자열로


    수도 코드
    배열의 각 요소 : 유저 정보
    응축하는 방법 (함수) : 하나의 유저의 이름과 쉼표를 이어 붙입니다(concat)
    원하는 형태 : 문자열로 누적합니다.
    응축된 결과 : 쉼표로 구분되는 모든 유저의 이름

     

    function joinName(resultStr, user) {
      resultStr = resultStr + user.name + ', ';
      return resultStr;
    }
    
    let users = [
      { name: 'Tim', age: 40 },
      { name: 'Satya', age: 30 },
      { name: 'Sundar', age: 50 }
    ];
    
    users.reduce(joinName, '');

     

    위 코드출력시 // 'Tim, Satya, Sundar, '

    위 결과에서 마지막 , 뒤에 공백까지 포함되어 있습니다.

    이는 reduce 메소드를 사용하여 모든 사용자의 이름을 합칠 때 발생하는 일반적인 동작입니다.

    필요에 따라 마지막 ,와 공백을 제거할 수 있습니다.

     

     

     


    # 배열을 객체로

     

    수도 코드

    • 배열의 각 요소 : 유저 정보
    • 응축하는 방법 (함수) : 유저 한 명의 이름 중 첫 글자를 주소록 객체 속성의 키(key)로,
    • 유저의 정보를 주소록 객체 속성의 값(value)으로 추가합니다.
    • 원하는 형태 : 주소록 객체에 누적합니다.
    • 응축된 결과 : 모든 유저의 정보가 알파벳으로 구분된 주소록

     

    [코드] 콜백 함수 makeAddressBook은 users 배열 안에 있는 요소로 주소록을 만듭니다.

    function makeAddressBook(addressBook, user) {
      let firstLetter = user.name[0];
      
      if(firstLetter in addressBook) {
        addressBook[firstLetter].push(user);
      } else {
        addressBook[firstLetter] = [];
        addressBook[firstLetter].push(user);
      }
    
      return addressBook;
    }
    
    let users = [
      { name: 'Tim', age: 40 },
      { name: 'Satya', age: 30 },
      { name: 'Sundar', age: 50 }
    ];
    
    users.reduce(makeAddressBook, {});

     

     

     

    코드 출력결과

    {
      'T': [
        { name: 'Tim', age: 40 }
      ],
      'S': [
        { name: 'Satya', age: 30 },
        { name: 'Sundar', age: 50 }
      ]
    }

     

    각 키는 사용자 이름의 첫 글자 (대문자)이며, 해당 키에 해당하는 사용자 객체가 배열로 그룹화되어 저장됩니다.

    'T'로 시작하는 이름을 가진 사용자는 Tim 한 명이며, 

    'S'로 시작하는 이름을 가진 사용자는 Satya와 Sundar 두 명입니다.

     


     

    # 고차함수와 추상화

     

    복잡한 어떤 것을 압축해서 핵심만 추출한 상태로 만드는 것이 추상화입니다. 

     

    추상화 = 생산성(productivity)의 향상

     

    한편 프로그램을 작성할 때, 자주 반복해서 사용하는 로직은 별도의 함수로 작성하기도 합니다. 

    이 역시 추상화의 좋은 사례입니다. 추상화의 관점에서 함수를 바라보면, 

    함수는 사고(thought) 또는 논리(logic)의 묶음입니다.

     

    아래의 getAverage 함수는 number 타입을 요소로 갖는 배열을 입력받아, 모든 요소의 평균값을 리턴합니다. 

    앞으로는 number 타입을 요소로 갖는 배열을 인자로 전달하기만 하면, 

    복잡한 로직은 신경 쓰지 않아도 평균값을 얻을 수 있습니다.

     

    [코드] getAverage 함수는 배열을 인자로 받아, 평균값을 리턴합니다.

    function getAverage(data) {
      let sum = 0;
      for (let i = 0; i < data.length; i++) {
        sum = sum + data[i];
      }
      return sum / data.length;
    }
    
    let output = getAverage([1, 2, 3]);
    console.log(output); // --> 2
    
    output = getAverage([4, 2, 3, 6, 5, 4]);
    console.log(output); // --> 4

     

     

    함수를 통해 얻은 추상화를, 한 단계 더 높인 것이 고차 함수입니다. 

    getAverage 함수는 값(배열)을 전달받아, 이 값을 가지고 복잡한 작업을 수행합니다. 

    이는 값 수준에서의 추상화입니다.

    함수 = 값을 전달받아 값을 리턴한다 = 값에 대한 복잡한 로직은 감추어져 있다 = 값 수준에서의 추상화


    고차 함수는 이 추상화의 수준을 사고의 추상화 수준으로 끌어올립니다.

    값 수준의 추상화: 단순히 값(value)을 전달받아 처리하는 수준
    사고의 추상화: 함수(사고의 묶음)를 전달받아 처리하는 수준
    다시 말해 고차 함수를 통해, 보다 높은 수준(higher order)에서 생각할 수 있습니다.

    고차 함수 = 함수를 전달받거나 함수를 리턴한다 = 사고(함수)에 대한 복잡한 로직은 감추어져 있다 = 사고 수준에서의 추상화
    추상화의 수준이 높아지는 만큼, 생산성도 비약적으로 상승합니다.

     

     

    [코드] 남성들의 평균 나이를 구하는 하나의 함수 getAverageAgeOfMaleAtOnce

    function getAverageAgeOfMaleAtOnce(data) {
      const onlyMales = data.filter(function (d) {
        // data.filter는 배열의 각 요소에 인자로 전달받은 함수를 적용하고,
        // 그 결과가 true인 요소만을 갖는 배열을 리턴합니다.
        return d.gender === 'male';
      });
    
      const numOfMales = onlyMales.length;
    
      const onlyMaleAges = onlyMales.map(function (d) {
        // onlyMales.map는 배열의 각 요소에 인자로 전달받은 함수를 적용하고,
        // 각 결과를 요소로 갖는 배열을 리턴합니다.
        return d.age;
      });
    
      const sumOfAges = onlyMaleAges.reduce(function (acc, cur) {
        // onlyMaleAges.reduce는 배열의 각 요소에 인자로 전달받은 함수를 적용하고,
        // 각 결과를 두 번째 인자로 전달받은 초기값(0)에 누적한 결과를 리턴합니다.
        return acc + cur;
      }, 0);
    
      return sumOfAges / numOfMales;
    }

     

    위에 제시된 getAverageAgeOfMaleAtOnce 함수는 배열 메서드를 적절하게 사용하여 순차적으로 원하는 작업을 수행합니다. 

    이 코드는 꽤 괜찮은 코드이지만, '남성'의 '평균 나이'만 구하는 작업에만 사용할 수 있습니다. 

    개선할 점을 찾아보면, 'male'을 매개변수화(parameterization) 하여 조금 더 일반적인(generic) 함수로 변경할 수 있습니다. 

    이렇게 수정하더라도, 어디까지나 '남성' 또는 '여성'의 '평균 나이'를 구하는 작업만 수행할 수 있습니다.

     

     

    한편, filter, map, reduce 등의 배열 메서드는 다른 목적을 위해서 사용될 수도 있습니다. 

    예를 들어 '남성' 중 '최연소 나이'를 구하거나,

     '여성' 중 '최연소 나이와 최연장 나이의 차이'를 구할 때,

     이미 작성된 로직을 그대로 쓸 수 있습니다.

    추상화는 고차 함수를 통해, 보다 쉽게 달성할 수 있습니다. 

    아래의 compose 함수는 입력받은 함수를 순서대로 결합하는 고차 함수입니다. 

    각각의 작업(filter, map, reduce)은 별도의 함수로 분리되어, compose의 인자로 전달되는 콜백 함수가 됩니다.

     

     

    function getOnlyMales(data) {
      return data.filter(function (d) {
        return d.gender === 'male';
      });
    }
    
    function getOnlyAges(data) {
      return data.map(function (d) {
        return d.age;
      });
    }
    
    function getAverage(data) {
      const sum = data.reduce(function (acc, cur) {
        return acc + cur;
      }, 0);
      return sum / data.length;
    }
    
    function compose(...funcArgs) {
      // compose는 여러 개의 함수를 인자로 전달받아 함수를 리턴하는 고차 함수입니다.
      // compose가 리턴하는 함수(익명 함수)는 임의의 타입의 data를 입력받아,
      return function (data) {
        // funcArgs의 요소인 함수들을 차례대로 적용(apply)시킨 결과를 리턴합니다.
        let result = data;
        for (let i = 0; i < funcArgs.length; i++) {
          result = funcArgs[i](result);
        }
        return result;
      };
    }
    
    // compose를 통해 함수들이 순서대로 적용된다는 것이 직관적으로 드러납니다.
    // 각각의 함수는 다른 목적을 위해 재사용(reuse) 될 수 있습니다.
    const getAverageAgeOfMale = compose(
      getOnlyMales, // 배열을 입력받아 배열을 리턴하는 함수
      getOnlyAges, // 배열을 입력받아 배열을 리턴하는 함수
      getAverage // 배열을 입력받아 `number` 타입을 리턴하는 함수
    );
    
    const result = getAverageAgeOfMale(data);
    console.log(result); // --> 26

     

     

     


     

     

     

    끝!!이아니고 문제풀러 😄

    댓글