쏙쏙 들어오는 함수형 코딩 CH2

Yunes·2023년 9월 24일
0
post-thumbnail

서론

현실적인 문제에 함수형 코딩을 어떻게 적용할 수 있는지에 대해 소개하는 챕터이다.

함수형 사고는 코드에서 쉽게 다룰 수 있는 부분과 조심히 다뤄야 할 부분을 명확하게 구분하는 것으로 시작한다. 19 p

의견
책에서 이 부분을 읽고 막연하다고 생각되던 액션, 계산, 데이터에 대한 이미지가 떠올랐다. 지금까지 JS 이든, Go 이든, Java 든 언어에 상관없이 저런 구분은 언제나 적용할 수 있으며 함수형 프로그래밍이 똑같은 코드라도 다른 구분을 적용하여 함수형 프로그래밍에 적합한 무언가 조치를 취할 수 있으리라는 생각이 들었다.

Part1 액션, 계산, 데이터

액션은 호출 횟수와 시점에 의존한다. 계산은 어떤 것을 결정하거나 계획하는 것으로 실행해도 다른 곳에 영향을 주지 않는다. 여기서 말하는 데이터는 변경불가능한 데이터로 결제, 재고, 피자 조리법 등에 해당한다. 19p

의견
이 책에서는 피자가게에 액션, 계산, 데이터라는 개념을 적용해서 설명하고자 한다. 나의 사례에 이 세가지 개념을 적용해보자면 redux 로 todo 앱을 최근에 만들어보게 되었다.

  • todo item 을 생성하는 순간 new Date 를 통해 id 값을 만들어서 할일을 추가하는 행위는 액션이다. 이 액션은 redux 의 action 이 아니다.

  • 필터 값에 따라 리스트에 띄울 할일을 결정하는 것은 계산이다.

  • 할일에 넣어둔 텍스트 ( 실제 할일 내용 )는 데이터 에 해당한다.

계층형 설계

코드를 자주 바뀌는 것으로부터 자주 바뀌지 않는 것으로 3단계로 나누어 각각을 비즈니스 규칙, 도메인 규칙, 기술 스택이라고 부르며 위에서부터 아래로 갈수록 의존성이 증가한다. 따라서 가장 위에 있는 코드는 쉽게 바꿀 수 있으며 이런 계층형 설계 로 만든 코드는 테스트, 재사용, 유지보수가 쉽다. 20p

이미지 출처 : https://www.yes24.com/Product/Goods/108748841

의견
8 ~ 9장에서 다행히 이 계층형 설계에 대해 자세히 이야기한다고 한다. 아직 위의 말로는 비즈니스 규칙, 도메인 규칙, 기술 스택을 각각을 나눈다는 것은 알겠어도 어떤 기준으로 비즈니스 규칙과 도메인 규칙을 구분해야 할지는 잘 감이 잡히지 않는다.

각 계층이 아래의 계층을 기반으로 만들어지니 아래의 계층으로 갈수록 의존성이 커지고 그만큼 자주 바뀌지 않아야 한다는 말은 이해가 잘 된다.

Part2 일급 추상

일급 추상 : 함수에 함수를 넘겨 함수를 재사용하는 것
일급 함수 : 함수가 다른 변수처럼 처리되는 함수 - mdn 일급 함수

일급 함수란

  1. 변수에 함수 할당
const foo = () => {
  console.log("foobar");
};
foo(); // Invoke it using the variable
// foobar
  1. 함수를 인수로 전달
function sayHello() {
  return "Hello, ";
}
function greeting(helloMessage, name) {
  console.log(helloMessage() + name);
}
// Pass `sayHello` as an argument to `greeting` function
greeting(sayHello, "JavaScript!");
// Hello, JavaScript!
  1. 함수를 반환
function sayHello() {
  return () => {
    console.log("Hello!");
  };
}

분산 시스템

이 책에서는 여러 대의 로봇이 함께 일을 하는 것을 분산 시스템이라고 말한다. 이를 위해 타임라인 다이어그램을 그려 문제를 파악하고자 했다.

타임라인을 서로 맞추지 않은 분산 시스템은 예측 불가능한 순서로 실행됩니다. 23p

의견
BE 코드를 짤때 비슷한 상황을 겪어본 적이 있었다. nest.js 로 데이터를 다루고 있었는데 분명 A 이후 B 가 처리되는 식의 순서가 나와야 하는데 B 가 먼저 처리가 되어 A 가 제대로 실행이 안되는 그런 상황이었다. 이런 경우 promise, then 혹은 async await 등을 통해 처리를 해줄 수도 있을 것 같다. 그리고 데이터베이스에서도 비슷한 경우로 transaction 처리를 할때 해당 최소 단위가 제대로 실행되면 commit 하고 문제가 있으면 rollback 하는 것도 이런 여러 타임라인간 순서를 제대로 지키기 위해 실행하는 바가 아닐까? 라는 생각이 들었다.

타임라인은 기본적으로 서로 순서를 맞추지 않는다. 액션이 실행되는 시간은 중요하지 않으니 각각의 타임라인은 다른 타임라인 순서와 관계없이 만들어야 한다. 가끔 순서가 어긋나는 경우가 발생할 수도 있으나 타임라인은 항상 올바른 결과를 보장해야 한다. 이런 시스템의 문제는 타임라인 다이어그램으로 알 수 있다. 24p

올바른 순서로 동작하는 프로그램을 만들기 위해서 시간에 의존적인 액션에 집중할 필요가 있다.

타임라인 커팅

타임라인 커팅 : 여러 타임라인이 동시에 진행될 때 서로 순서를 맞추는 방법으로 고차 동작으로 구현한다.

고차 함수 HOF

고차 함수 high-order function HOF : 하나 이상의 함수를 인자로 받거나 함수를 결과로 반환하는 함수

의견
여기서 고차함수랑 일급함수랑 무슨 차이지? 라는 생각이 들었다.

일급 추상 이라는 것도 일급 객체의 특징중 하나인 것이다. 위에서 일급 함수가 함수의 인자로 전달될 수 있거나 함수의 반환값에 사용할 수 있거나 변수에 담을 수 있는 함수를 말한다고 설명했다.

그런 일급 객체인 함수를 인자로 사용하거나 결과값으로 반환하는 함수를 고차함수라고 하는 것이다. 두가지 특징을 모두 갖고 있어도 마찬가지로 HOF 이다.

고차함수는 인자로 받은 함수를 필요한 시점에 호출하거나 클로저 를 생성하여 반환한다. JS 의 함수가 일급객체이니 값처럼 인자로 사용하거나 반환하거나 전달할 수 있다. poiemaweb 5.30 배열 고차 함수

// 함수를 인자로 전달받고 함수를 반환하는 고차 함수
function makeCounter(predicate) {
  // 자유 변수. num의 상태는 유지되어야 한다.
  let num = 0;
  // 클로저. num의 상태를 유지한다.
  return function () {
    // predicate는 자유 변수 num의 상태를 변화시킨다.
    num = predicate(num);
    return num;
  };
}

// 보조 함수
function increase(n) {
  return ++n;
}

// 보조 함수
function decrease(n) {
  return --n;
}

// makeCounter는 함수를 인수로 전달받는다. 그리고 클로저를 반환한다.
const increaser = makeCounter(increase);
console.log(increaser()); // 1
console.log(increaser()); // 2

// makeCounter는 함수를 인수로 전달받는다. 그리고 클로저를 반환한다.
const decreaser = makeCounter(decrease);
console.log(decreaser()); // -1
console.log(decreaser()); // -2

여기서 클로저라 한 것은 makeCounter 를 outer lexical scope 로 보고 그 내부에 리턴되는 함수는 lexcial environment 로 makeCounter 를 포섭 (closure) 하여 makeCounter 가 선언된 이후 종료되었음에도 클로저가 생성되어 있는 상태이니 makeCounter 를 global 에서 호출했는데 리턴되는 함수가 기억하는 lexical scope 에 있는 num 을 참조하여 1 씩 증가, 1씩 감소하는 동작을 보인 것이다.

Higher Order Function 이란 무엇인가 에 고차함수에 대해 정말 정말 정말 잘 설명이 되어있다. 이 글을 읽고 좀 이해가 되었는데 해당 글에서는 고차함수가 어떻게 활용되고 왜 유용한지에 대해 설명이 되어있다.

HOF 는 단순히 함수의 값을 전달하는 기존 관념을 넘어 함수의 흐름을 제어하는 파라미터로써 수용한다. 이를 제어패턴 추상화 Abstracting Patterns of Control 이라고 부른다.

예를 들어 요구사항이 자주 변경될 수 있는 경우 기존 관념대로 함수의 인자로 값을 전달한다면 각 요구사항별로 다른 함수를 만들어줘야 한다.

함수의 구조를 좀더 유연하게 만들기 위해 HOF 를 활용할 수 있다.

위의 글에서는 같은 반복문에서 인덱스를 출력하거나 인덱스를 배열에 담아 반환하라는 요구사항이 있을 경우 해당 비즈니스 로직을 일급객체로 만들어 고차함수에 인자로 전달하는 경우를 예로 들고 있었다.

function repeat(n, fn) {
    for (let i = 0; i < n; i++) {
        fn(i);
    }
}

// 0부터 9999까지 출력하기
repeat(10000, console.log);

// 0부터 9999까지 배열에 담기
const list = [];
repeat(10000, (i) => {list.push(i)});

/////

function repeat(n, fn, interval) {
    for (let i = 0; i < n; i = interval(i)) {
        fn(i);
    }
}

repeat(100, console.log, i => i === 0 ? ++i : i+i); // 0 1 2 4 8 16 32 64

이 부분에 이어서 고차함수의 특징중 일급객체를 반환한다는 부분에 대해 설명을 하고 있었는데 이게 React 와 관련지어 생각해볼 수 있었다.

function fillArray(n, fn) {
    let array = [];
    for (let i = 0; i < n; i++) {
        array.push(fn(i));
    }
    return array;
}

const array = fillArray(7, (i) => `item${i}`);

console.log(array); // [ 'item0', 'item1', 'item2', 'item3', 'item4', 'item5', 'item6' ]

위 글에서 예시로 보여준 함수인데 이것만 보고는 왜 일급객체인지 생각을 해보다가

function fillArray(n, fn) {
    let array = [];
    for (let i = 0; i < n; i++) {
        array.push(fn(i));
    }
    return array;
}

function makeItem(i) {
    return (i) => `item${i}`;
}

const array = fillArray(7, makeItem);

console.log(array); // [ 'item0', 'item1', 'item2', 'item3', 'item4', 'item5', 'item6' ]
view raw

이렇게 변환을 해주니 함수를 반환하고 있다는 사실이 좀더 명확하게 눈에 들어왔고 이 구조가 React 에서 () => {} 화살표함수로 이벤트 핸들러를 전달하거나 props 로 functional component 를 전달하는 High Order Component 등이 이와 관련이 있는 부분이었다. 사실 Array 에 관련된 filter, map, reduce 등도 인자로 콜백함수를 전달하고 있으니 이 함수들도 HOF 였었다.

⭐️ 이렇게 보니 고차함수라는게 함수의 동작을 정말 유연하게 만들어 줄 수 있는 좋은 기능을 할 수 있다는 사실을 알게 되었다.

다시 책의 내용으로 돌아와서,

타임라인 커팅으로 서로 다른 작업들과 시간에 따라 진행하는 작업을 쉽게 이해할 수 있다. 타임라인 다이어그램은 유연하다. 26p

생각
이 내용을 읽고 비슷할지는 모르겠는데 최근에 redux 를 활용한 todo 앱 을 만들어 보면서 기존과 다른 방식으로 코드를 짰던 기억이 났다. 기존엔 어떠한 요구사항이 있다면 그에 맞춰 코드를 짜기 시작했는데 이번에 redux 를 활용하면서 어떻게 시작해야 할지 감이 잘 잡히지 않았다. 그래서 docs 에서 소개해주는 순서대로 요구사항을 먼저 분석하고, 이 요구사항을 만족시키기 위한 액션의 종류와 필요할 상태 객체와 상태 구조를 생각했다. 그러고 나서 코드를 짜기 시작했는데 이러한 과정이 이전에 시도하다가 감이 잘 잡히지 않아 어려웠던 설계 과정과 유사했다. 이처럼 함수형 프로그래밍도 분산형 구조로 코드를 짤때 먼저 타임라인 다이어그램을 그리며 각 작업들에 대해 먼저 이해를 하고 코드를 짜기 시작하는가? 라는 생각이 들었다.

머릿속으로만 생각하기 보다 액션을 시각화해서 나열해보면 허점을 더 잘 찾아내서 보다 안정적인 코드를 짤 수 있을 것 같다.

레퍼런스

cousre
부스트코스 함수형 프로그래밍 패러다임
reference
Haegul Pyun - Higher Order Function 이란 무엇인가
poiemaweb 배열 고차 함수

profile
미래의 나를 만들어나가는 한 개발자의 블로그입니다.

0개의 댓글