Posts 선언형프로그래밍
Post
Cancel

선언형프로그래밍

선언형, 명령형 그리고 추상화

선언형이란?

명령형은 어떻게(How)에, 선언형은 무엇(What)에 집중한다.

선언형은 명령형 코드에서 ‘어떻게’를 감추고 ‘무엇을’만 노출하는 방식의 추상화이다. 일종의 리팩토링이다.

예시를 들면 다음과 같다.

1
2
3
4
5
6
7
8
// 명령형 코드 : 배열에 있는 모든 숫자를 하나씩 제곱해서 result 배열에 넣는다.
function double(arr) {
  let results = [];
  for (let i = 0; i < arr.length; i++) {
    results.push(arr[i] * 2);
  }
  return results;
}
1
2
3
4
// 선언형 코드 : 모든 배열에서 숫자가 제곱된다.
function double(arr) {
  return arr.map((item) => item * 2);
}

배열의 모든 요소를 돌며 하나씩 제곱하는 것이 아니라, 배열의 각 요소를 대상으로 제곱이 되라고 선언한 형태이다. How는 map 함수 안에 숨겨져있다.


함수로 묶으면 다 선언적인가?

선언형 프로그래밍의 대중적인 정의 몇가지

  • A high-level program that describes what a computation should perform.
  • Any programming language that lacks side effects (or more specifically, is referentially transparent)
  • A language with a clear correspondence to mathematical logic.

여기서 두 번째 불렛의 사이드 이펙트가 적고 순수하다라는 점이 중요하다.

1
moveToEmptyTable(myPosition, tables);

위 함수는 충분히 선언적이지 못하다.

  1. 먼저 순수하지 못하다. 빈자리를 얻어내는 계산함수와 , 빈자리에 배치하는 액션함수가 하나의 메소드로 묶여있다.
  2. 여러 번 호출하거나, 특수한 상황에서 호출했을 때 다른 결과물을 줄 수 있다. 따라서 재사용이 어렵다. 이는 계산과 액션이 묶여있기에 발생한다.
    • 빈자리에 이미 앉아있는 경우에 moveToEmptyTable()을 호출한다면 빈자리에 찾아갈지 아니면 오류가 날지 모른다.

따라서 언제 불러도 같은 결과가 나오도록, 또 순수해지도록 리팩트링하면 다음과 같이 두 함수로 나뉠 수 있다.

1
2
const emptyTablePosition = getEmptyTablePosition(tables);
move(me, emptyTablePosition);
  • 코드의 절차적인 순서와 상관없이 언제 어디서 불러도 동일한 결과값을 준다.(재사용성 up)
  • what이 함수명에 적절히 표현되어있다.
  • 세부 구현은 함수 내부에 추상화되었다.


선언적 프로그래밍의 또다른 특징 : 코드순서 노상관

라인 바이 라인의 코드순서가 중요하지 않아질수록 더 선언적이게 된다.

순서의존도가 없기 때문에 사이드이펙트도 줄어들고 이해하기도 더 쉬워진다.

예를들어 리액트 컴포넌트의 props은 순서에 상관없이 동일한 동작을 한다.

1
2
3
4
5
<Modal
    title="뭐먹지"
    onClick={() => alert("짬뽕")}
    description="중국음식?"
/>

여기에 title, onClick, description의 코드 순서는 중요하지 않다.


명령형 추상화, 선언형 추상화

다음은 동,읍,면 Input이 있고, 동을 입력하면 자동으로 읍으로, 읍을 입력하면 자동으로 면으로 넘어가는 코드이다.

1
2
3
4
5
6
7
8
9
10
11
// 명령형 코드 원본
if (value.length < maxLength) {
  return;
}
if (cursorPosition === "") {
  읍input.focus();
  읍input.selectionStart = 읍input.value.length;
} else if (cursorPosition === "") {
  면input.focus();
  면input.selectionStart = 면input.value.length;
}
1
2
3
4
5
6
7
8
9
10
// 명령형 추상화
const isInputFull = value.length === maxLength;
if (!isInputFull) {
  return;
}
if (cursorPosition === "") {
  moveToInput("");
} else if (cursorPosition === "") {
  moveToInput("");
}
1
2
3
4
// 선언형 추상화
<Input name="" onFull={() => moveFocusTo('')}/>
<Input name="" onFull={() => moveFocusTo('')}/>
<Input name=""/>


정리

명령형 코드를

  • What을 적절히 인터페이스에 노출하면서
  • How를 내부에 숨기고
  • 언제 어디서 불러도 동일한 결과가 나와서 재사용하기 편하게 추상화한다면

선언적 코드라고 볼 수 있다.


명령형 코드를 해석할 때는, 흐름을 따라가면서 읽어야 하는 시간축이 있다. 하지만 선언적코드는 흐름에 따라 읽지 않아도 되기에 읽기도, 디버깅하기도, 재사용하기도 좋다. 또한 내부를 몰라도 사용할 수 있다는 점에서 사용성이 좋다.

선언적 프로그래밍은 코드 자체에 어떤일이 벌어진지 설명하기 때문에, 좀 더 추론하기 좋다.

아래 예시를 통해 알아보자

1
2
3
4
5
6
7
8
9
10
11
12
const loadAndMapMembers = compose(
  combineWith(sessionStorage, "members"),
  save(sessionStorage, "members"),
  scopeMembers(window),
  logMemberInfoToConsole("name.first"),
  countMemberBy("location.state"),
  prepStatesForMapping,
  save(sessionStorage, "map"),
  renderUSMap
);

getFakeMembers(100).then(loadAndMapMembers);

많은 함수가 결합되어있지만, 선언형으로 되어있기에 각 함수가 어떻게 구현되어있을 거라고 추론하기 쉬워진다.


출처

https://milooy.github.io/dev/220810-abstraction-and-declarative-programming/

This post is licensed under CC BY 4.0 by the author.

React-query 에서 cache를 사용하는 방법

바벨

Comments powered by Disqus.