본문 바로가기
공부/JS

함수형 자바스크립트 훑어보기

by Piva 2024. 11. 26.
  • 함수형 프로그래밍이 정확히 어떤 것일까 했는데, 자세히 공부하려고 사놨던 책(함수형 자바스크립트)을 이제서야 읽어보기 시작했다(ㅎㅎ).
  • 이전의 내 짧은 지식으로는 함수를 위주로 구성하는 프로그래밍(?) 정도의 얕팍한 이해 정도만 하고 있었는데, 1장을 읽어보며 조금의 이해를 쌓아보기로.

함수형 프로그래밍이 무엇인가요?

  • 말 그대로 ‘함수의 사용을 강조하는 프로그래밍 스타일’이다.
  • 다만, 단순히 ‘함수를 사용하자’는 것은 아니고 고려하고 반영해야 하는 원칙이 존재한다.

 

함수형 프로그래밍의 원칙

  • 부수 효과 (Side Effect)를 방지한다.
  • 상태 변이를 줄이기 위해 데이터의 제어 흐름 및 연산을 추상화한다.

⇒ 이 원칙을 준수하기 위해 ‘재사용성 & 신뢰성(Reliability)’을 고려하여, 가능한 작게 기능을 나눈다.

 

 

FP(Functional Programming)의 기본 개념

선언적(Declarative)이다

  • 선언적 프로그래밍에서 ‘이 로직을 어떻게 구현하는가?’ 는 중요하지 않다.
  • 내부 매커니즘은 추상화되어있는 채로, 이 로직의 결과가 어떻게 나오는지에 집중한다.
  • JavaScript에서는 이를 구현할 때, 선언적 함수인 HoF(Hider order Function)을 사용하여 구현한다.
    • HoF에는 map, filter, reduce 같은 함수들이 존재한다.

ex) 숫자로 이루어진 배열의 각 요소에 1씩 더한다고 가정할 때. 명령형 프로그래밍과 함수형 프로그래밍은 아래와 같이 구현할 수 있다.

 

  • 명령형 프로그래밍
const arr = [1, 2, 3, 4, 5];

for (let i = 0; i < arr.length; i++) {
	arr[i] += 1;
}

→ 각 배열 요소에 어떻게 접근할지, 접근한 배열 요소에 어떤 작업을 해야 하는지에 대해 상세히 서술되어있다.

 

  • 함수형 프로그래밍
const arr = [1, 2, 3, 4, 5];

arr.map((el) => el + 1);

→ 각 배열 요소에 접근하는 등의 세세한 로직은 생략되고, 오직 배열 요소에 ‘무엇을 할지(여기서는 1을 더하기)’에 대해 서술되어있다.

 

 

무상태성 & 불변성을 지향한다

  • 위의 예제 명령형 프로그래밍 예제 코드에서, 원본 배열 arr의 값들은 반복문 실행 후에 바뀌게 된다.
    • 명령형 프로그래밍에서 루프와 같은 구조는 반복할 때마다 값/상태가 계속 바뀌고, 재사용이 어렵다.
  • 함수형 프로그래밍 코드에서는 map함수를 통해 원하는 동작이 배열 각 요소에 실행된 새로운 배열이 반환된다.
    • 즉, 원본 배열은 변하지 않는다.
    • 이러한 방식으로, 함수형 프로그래밍은 상태나 값을 바꾸지 않는 무상태성과 불변성을 지향한다.
  • 무상태성과 불변성을 지키기 위해서, ‘순수함수(Pure Function)’를 사용한다.

 

순수함수

순수함수는 아래와 같은 특징을 갖는다.

  1. 외부 값과는 부관하게 작동한다.
  2. 외부 값이나 매개변수를 변경하지도 않는다.
  3. 입력 값에만 의존한다.
  4. 부수 효과(Side Effect)를 일으키지 않는다.
부수효과란, 아래와 같은 것들을 지칭한다.

- 전역 변수나 속성 등을 변경한다.
- 예외 처리 없이 throw 한다.
- 매개변수를 변경한다.
- HTML 문서, 브라우저 쿠키, DB에 질의한다.
- 화면이나 로그를 출력한다.

→ 위와 같은 부수효과를 전부 근절하기란 어렵지만, 상태 변이를 최소화하면서 순수 함수의 사용을 지향하자는 것이 핵심.

 

 

참조 투명성 & 치환성

  • 참조 투명성이란 ‘어떤 함수가 동일한 입력을 받았을 때, 동일한 결과를 내는 성질’을 의미한다.
    • 순수 함수의 조건이기도 하다.
// Example
let count = 0;

const increment1 = () => { return ++counter }; // 참조 투명하지 않다.
const increment2 = (count) => { return count + 1 }; // 참조 투명하다.
  • 코드를 테스트하기 쉽고, 전체 로직 파악이 쉽다는 이점이 있다.
    • 참조 투명하지 않은 함수는 외부 요소의 변화에 따라 영향을 받기 때문에, 흐름을 파악하기 어렵다.
  • 참조 투명하게 구현된 프로그램은 재작성 혹은 치환 시에도 원하는 결과를 얻을 수 있다.

 

불변 데이터의 유지

  • 기본적으로 JavaScript의 기본형은 모두 불변하다.
    • 그러나 객체(ex. 배열 등)는 불변하지 않기 때문에, 뜻하지 않게 부수효과를 낳을 수 있다.
  • 함수형 프로그래밍은 모든 변수를 ‘불변하게’ 관리한다.

 

함수형 프로그래밍의 좋은 점

  1. 간단하고 작은 함수들로 작업을 나눈다.
  2. 흐름 체인(Fluent Chain)으로 데이터를 처리한다.
  3. 리액티브 패러다임을 실현하여 이벤트 중심 코드의 복잡성을 줄인다.

 

작업의 분해

  • 함수형 프로그래밍의 하위 작업의 단위는 ‘함수’이다.
    • 즉, 작업을 작은 함수로 쪼개나간다는 뜻이다.
    • 이 때, 함수는 한 가지 목표(기능)만을 실행해야 한다.
      단일성을 추구해야 한다.

 

체이닝(Chaining)

  • 체인: 같은 객체를 반환하는 순환적인 함수 호출을 일컫는다.
    ex) Lodash를 사용해 복수과목을 수강한 학생들의 평균 점수를 계산하는 로직은 아래와 같이 표현할 수 있다.
import * as _ from 'lodash';

_.chain(enrollment)
  .filter(e => e.enrolled > 1)
  .pluck('grade')
  .average()
  .value();
  • 함수 체인은 필요한 시점까지 함수의 실행을 미루는 ‘느긋한 평가’를 수행한다.

 

비동기 어플리케이션에서의 처리(+ 리액티브 패러다임)

  • 리액티브 패러다임(혹은 프로그래밍): 데이터의 흐름과 변경에 중점을 두는 선언적 프로그래밍 방식이다.
    • 데이터의 변화를 Observer를 통해 관찰하고, 이벤트 발생 시 로직을 수행한다.
    • 코드를 추상화하여 비즈니스 로직에만 집중할 수 있게 해준다.
  • 함수형 프로그래밍과 리액티브 프로그래밍을 같이 사용함으로써, 데이터의 흐름과 변경 로직을 간결하고 가독성 있게 구현할 수 있다. ⇒ 복잡성이 줄어든다.

  • 읽다보며... 내 평소 스타일은 함수형 프로그래밍의 그것에 가까우나, 별개로 ‘그래서 함수들을 어떻게 만들고 관리해야 하는지’에 대해서는 알고 있는게 없어서 그닥 제대로 된 함수형 프로그래밍을 해왔단 생각은 들지 않았다.
  • 함수형 프로그래밍의 추구 목표와 그 특성에 대해 간단히 살펴봤으니, 남은 부분을 천천히 읽어보며 어떻게 코드를 개선할 수 있을지 고민해봐야겠다.