Skip to main content

함수형 코딩

함수형 코딩, 에릭 노먼드

챕터 1, 함수형 프로그래밍 인트로

함수형 프로그래밍

  • 정의 : 수학함수를 이용하여 부수효과 없이 프로그래밍 로직을 작성하는 것. ( 순수함수 이용, 부수효과 없음 )
    • 순수함수만으로 논리전개는 학문적으로는 가능해도, 현실 세계에서는 부수효과가 많을 수 밖에 없다.
    • 부수효과는 비수순수 함수 이며, 이메일 전송, 현재 시간 구하기 등 시점과 횟수에 따라 다른 결과가 나오게 된다.

함수형 사고는 액션, 데이터, 계산 3가지로 구분한다.

  • 데이터는 이벤트의 상태를 기록한 자료이다.
  • 계산은 시점과 호출 횟수에 상관없이 동일한 입력이면 동일한 출력을 낸다.
  • 액션은 부수효과를 가진 로직이다. eg, 이메일 전송, 현재시간 구하기, 데이터 베이스 저장 등등

함수형 코딩에서는 액션을 잘 관리하는것이 중요하다.

  • 부수효과를 잘 관리하는 여러가지 유틸을 사용하여 관리한다.
  • 최대한 데이터와 계산 로직으로 처리하면서, 액션을 다루면 예상치못한 애러를 피할 수 있다.

챕터 2, 함수형 프로그래밍 원칙

📌 원칙1. 액션, 데이터, 계산 구분

  • 액션 : 시점과 호출 횟수에 따라 다른 결과가 나오는 로직

  • 데이터 : 불변성을 가지고 있는 데이터

  • 계산 : 순수 함수 로직으로, 계산을 여러번 반복해도 문제가 되지 않는다.

  • 계층화 설계 : 자주 변경되는 부분과 그렇지 않은 부분을 나눈다.

    • 1.비즈니스 규칙 : 매주 바뀌는 규칙들
    • 2.도메인 규칙 : 기술스택을 가공하는 로직
    • 3.기술 스택 : 자료구조 등
      • *비즈니스 규칙은 도메인 규칙을 사용한다. 도메인 규칙은 기술 스택을 사용한다. (모듈의 의존성의 방향)
      • *장점 : 재사용, 테스터블, 유지보수

📌 원칙2. 일급 추상

  • 일급 함수는 함수를 인자로 받는 함수이다.
  • 함수자체를 변수로 받아서 처리하는것 자체가 함수에 대한 의존성 주입을 받겠다는 의미이다.
  • 재사용, 테스트, 유지보수 용이 - 3단 이점이 생긴다.
  • 예) 피자 주문 관리
    • 주문, 반죽, 소스, 굽기, 출고 과정들은 일부는 병렬 실행 가능, 일부는 이전 작업에 의존한다.
    • 이전 작업을 기다리는 것은 커팅(cutting) 으로 가능하다.
    • 이러한 복잡한 과정들은 타임라인 다이어그램으로 시각화 가능하다.
    • 위 각각의 테스크들을 함수라는 단위로 바라보고, 전체 실행과정은 선언적으로 프로그래밍 가능함.
  • 일급 추상 장점 : 분산 및 병렬 처리, 동시성 관리, 선언적 프로그래밍 - 3단 이점

📌 선언형 vs 명령형

  • 선언적 프로그래밍 : 무엇을 달성할지를 기술하는 방식 (목표 명시 코딩)
  • 명령형 프로그래밍 : 어떻게 해야하는지 상세히 기술.
// 명령형: 어떻게 반복할지 직접 작성
let result = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] > 10) {
result.push(arr[i]);
}
}

// 선언적: 무엇을 할지 함수로 전달
const result = arr.filter(x => x > 10);

챕터 3, 액션과 계산, 데이터의 차이 및 설계

📌 현실세계 문제를 액션, 계산, 데이터로 나누어서 모델링하는 과정은 반드시 연습이 필요하다.

문제 정의 : 액션, 계산, 데이터로 구분하여 모델링

  • 문제 해결을 위한 타임라인 다이어그램으로 미리 설계를 하면 도움이 많이 된다.
  • 1, 특히 처음 함수형 코딩을 생각하면 액션만 떠오른다. 계산은 머리속에서 자연스럽게 발생하기 때문이다.
  • 2, 액션에서 계산과 데이터를 더 분리할 수 있는지 생각한다.
  • 3, 계산은 더 작은 계산들로 나누어 질 수 있다.
  • 4, 액션과 계산은 입출력으로 데이터를 받는다.
  • 코드 구현 : 최대한 계산으로 빼는것이 중요.
  • 코드 파악 : 액션, 데이터, 계산로 구분하며, 숨어있는 액션과 액션간의 순서를 파악.

📌 데이터의 정의 및 디테일

데이터의 정의 : 이벤트에 대한 사실을 기록한 것.

  • 예) User Entity : 유저가 어떻게 이벤트의 기록이냐? 데이터 베이스에 저장된 유저 정보는 create User 이벤트에 의한 결과를 적은 사실이다.

  • 데이터의 단점 : 해석이 필요하다.

  • 데이터의 장점 :

    • 1.직렬화 가능 (클라이언트 데이터나 DB의 데이터나 동일하다. 반면 액션이나 계산은 그렇지 못한 가능성이 있다.)
    • 2.비교 연산 가능
    • 3.해석의 자유 : 해석이 필요한것은 단점이지만, 그레서 해석의 자유를 가지는것은 장점이다.
  • 자료구조의 선택 : 만약에 순서가 중요한 데이터라면 순서가 보장되는 자료구조로 저장하면 된다.

  • 불변성 유지 방법 :

    • 1.데이터 변경 시점에 복제하기 ( copy on write )
    • 2.데이터 저장 시점에 복제하기 ( defensive copy )

📌 계산의 정의 및 디테일
계산의 정의 : 호출 시점이나 횟수에 상관없이 동일한 입력에 대해서 같은 출력값을 내는 함수
계산의 장점 : 3단 이점 (테스트, 재사용, 유지보수),

  • 기계적 분석(정적분석)이 가능하다
  • 재사용성 -> 일급 함수로 조합하기 좋다.
    계산의 단점 : 실행하기 전에 어떤 일이 발생할 지 모른다. 실제로 돌려봐야 안다.
  • 이게 싫다면 -> 데이터는 사건의 기록이므로 데이터를 사용해야 한다.

참고로 비동기 함수는 액션으로 분류한다. 언제 끝날지 Promise(미정) 때문이다.

📌 액션의 정의 및 디테일

액션의 정의 : 외부세계에 영향을 주는것 혹은 받는 것을 말한다. 비순수 함수, 사이드 이펙트가 있는 함수
액션은 반드시 필요하며, 이것이 SW를 사용하는 이유이다.

  • 1, 액션은 가능한 적게, 작게 만들어야 한다.
  • 2, 바깥쪽에서 액션을 만들고, 안쪽에서는 계산을 하는 어니언 아키텍처를 지향
  • 3, 액션이 호출 시점에 의존하는것을 지양 ?

📌 연습문제 사고 개선 - 장보기
1.당신은 식료품 사장입니다. 빠진 재고를 채우기 위해 장보는 과정을 함수형 사고 관점에서 생각해 보세요.

👃 As Is

  • 생각의 초안 : 장보는 과정들을 액션으로만 나열했음
    • 액션 : 냉장고 확인 > 마트 출발 > 물건 구매 > 집 출발
  • 그 과정에서 '현재 재고', '목표 재고' 라는 원천 데이터 정의를 함 (데이터 정의)
  • 목표 재고 + 현재 재고를 입력으로 '계산'을 통해서 '장보기 목록' 이라는 데이터를 도출. (계산 도출)

📌 연습문제 사고 개선 - 쿠폰

  • 👃 이메일 데이터 베이스 및 쿠폰 데이터베이스를 읽을때 good, best 쿠폰 조건에 맞는 이메일 리스트를 가져오려고 함
  • 🟢 책에서는 모든 이메일 리스트, 쿠폰 리스트를 가져온 후 '계산을 통해서' good, best 쿠폰 목록 데이터를 처리함.

2.복잡한데 그냥 액션으로만 처리하면 안되나요?

  • 만약에 테스트를 해야하는 상황이라면, 매번 best, good 쿠폰이 사용자에게 직접 보내면서 테스팅을 할수 없다.
  • dry run 처럼 '계산'을 통해 best, good 쿠폰 전송 대상 이메일 목록를 추출하고 검증해야 한다.

챕터 4, 액션에서 계산 분리하기

📌 액션에서 계산 분리하는 과정

액션에서 암묵적인 입력과 출력 찾아내기

  • 암묵적 입력 : 전역변수 읽기 등
  • 암묵적 출력 : 전연변수 쓰기, 콘솔 로그 등
    • 위 입출력은 함수의 파라미터나 리턴값으로는 예측하기 어려운 동작들이다.
    • 값은 불변성을 지키기 (쓰거나 읽을때 객체를 복사 한다.)

액션에서 계산 코드로 추출하면?

  • 코드가 늘어난다. 재사용하지 않는 계산함수가 늘어나도 괜찮다.

챕터 6, 변경 가능한 데이터 구조를 가진 언어에서 불변성 유지하기

📌 얇은 복사, 중첩 데이터, 구조적 공유

  • 객체를 원소로 하는 배열을 얇은 복사하면 배열 껍대기만 하나 더 만들어진다.
  • 이때 배열의 이터레이션을 돌면서 객체 원소를 수정하는 상황에서 불변성을 유지하려면, 객체를 복사해서 값을 변경하면 된다.
  • 그러면 배열의 모든 요소인 객체가 재생성되지 않고도 불변성을 유지하는 결론이 된다.
  • 마치 리액트의 diff 알고리즘을 통해서 변경된 부분만 유지하는것과 유사하다.
  • 불변성 유지가 객체 복사로 인해서 많은 성능 우려가 있지만, 얇은 복사와 구조적 공유을 통해서 최적화 가능하다.

챕터 7, 신뢰할 수 없는 코드를 쓰면서 불변성 지키기

📌 깊은 복사, 안전 지대, 비공유 아키텍처

  • 레거시 코드는 변경할 수 없는 기존의 코드이다. 불변성 원칙을 신뢰할 수 없는 위험지대에 있다.
  • 안전지대에서는 불변성 원칙이 지켜지는 바운더리이다. 만약에 레거시 코드의 함수를 호출해야 한다면 불변성이 깨질 수 있다.
  • 이때는 깊은 복사를 통해서 안전지대에 데이터를 가져와야 하고, 안전지대에서 데이터가 나갈 때도 깊은 복사를 통해서 데이터를 보내야 한다.
  • 대표적인 예시로 REST API에서 JSON을 주고 받을때 이다. 서버에서 데이터를 받으면 문자열을 파싱해서 객체를 만든다.
  • 노드간에 응답,요청 모두 JSON 직렬화를 통해서 데이터 자체의 참조가 공유되지 않는다. 이를 비공유 아키텍처라고 한다.
  • 깊은 복사는 얕은 복사보다 비싼 연산이지만, 안전지대를 보호하기 위해서 얕은복사보다는 적은 횟수로 특별히 사용되어야 한다.