프로그래밍 패러다임(Programming Paradigm)

프로그래밍 패러다임이란?

프로그래밍 패러다임이란,
특정 시대의 어느 개발자 공동체에 의해 수용된 프로그래밍 방법과 문제 해결 방법, 프로그래밍 스타일이라고 할 수 있다.

우리가 어떤 프로그래밍 패러다임을 사용하느냐에 따라 우리가 해결할 문제를 바라보는 방식프로그램을 작성하는 방법이 달라진다.

프로그래밍 패러다임의 필요성

프로그래밍 패러다임은 개발자 공동체가 동일한 프로그래밍 스타일과 모델을 공유할 수 있게 함으로써 불필요한 부분에 대한 의견 충돌을 방지한다.
또한 프로그래밍 패러다임을 교육시킴으로써 동일한 규칙과 방법을 공유하는 개발자로 성장할 수 있도록 준비시킬 수 있다.

한 번에 하나의 패러다임만 적용해야 할까?

두 패러다임(혹은 그 이상)이 함께 존재할 수 없는 것은 아니다.
오히려 서로 다른 패러다임이 하나의 언어 안에서 공존함으로써 서로의 장단점을 보완하는 경향을 보인다.

따라서 특정 패러다임을 주로 사용한다고 하더라도 다른 패러다임을 배우는 것이 도움이 된다. 해당 패러다임이 적합하지 않은 상황에서는 언제라도 다른 패러다임을 적용할 수 있는 시야를 기르고 지식을 갈고 닦아야 한다.

대표적인 예로 함수형 패러다임과 객체지향 패러다임을 접목시킨 스칼라(Scala)가 있다. 이처럼 하나 이상의 패러다임을 수용하는 언어를 "다중패러다임 언어(Multiparadigm Language)" 라고 부른다.

프로그래밍 패러다임은 발전적(evolutionary)이다.

프로그래밍 패러다임은 과거에 있던 패러다임의 단점을 보완하는 발전적인 과정을 거치는 것으로 보인다. 그래서 프로그래밍 패러다임은 발전적(evolutionary)이다.

  • 출처: 조영호, 『오브젝트』, 유키북스(2019)

함수형 프로그래밍(Functional Programming)이란?

✏️ 함수형 프로그래밍이란,

"순수 함수(pure function) 를 조합하고,
공유 상태(shared state)변경 가능한 데이터(mutable data) 부작용(side-effects) 을 피하여 소프트웨어를 만드는 프로세스입니다.
함수형 프로그래밍은 명령형(imperative)이 아닌 선언형(declarative)이며, 애플리케이션의 상태순수 함수를 통해 전달됩니다."

-https://sungjk.github.io/2017/07/17/fp.html


객체지향 프로그래밍(OOP) vs. 함수형 프로그래밍(FP)

OOP에서는 애플리케이션의 상태가 일반적으로 공유되고 객체의 메서드와 함께 배치된다.
반면 FP에서는 상태 공유를 하지 않으며, 공유 상태(shared state)를 피하면, 함수 호출의 타이밍과 순서는 함수 호출의 결과를 변경하지 않는다.

즉 FP는 순수 함수를 사용하는 것이고, 순수 함수는 동일한 입력에 대해 항상 동일한 출력을 얻을 수 있다. 이렇게 하면 해당 함수를 다른 함수 호출과 완전히 독립적으로 호출할 수 있으므로 변경과 리팩토링을 근본적으로 단순화할 수 있다. 하나의 함수에 변경이 있거나, 호출 타이밍이 변경되어도 프로그램의 다른 부분을 깨뜨리지 않는다.


함수형 프로그래밍의 특징

1. Side Effect를 없앤다. = 순수 함수(Pure Function)를 사용한다.

함수는 함수의 역할에만 충실할 뿐, 인자의 값을 변경하거나 프로그램의 상태를 변경하는 등 다른 부수효과(Side Effect)를 만들어내서는 안된다.
이 말은 다시말해, 순수함수(Pure Function)를 사용해야 한다는 뜻과 같다.

🌈 순수함수(Pure Function)란 아래의 조건들을 만족하는 함수:

  • 동일한 입력에는 항상 같은 값을 반환해야 한다. → 입력값으로만 동작하며, 입력값에 대한 결과를 예측할 수 있다.
  • 함수의 실행은 프로그램의 실행에 영향을 미치지 않는다. → 함수 내부에서 일어나는 어떠한 행동도 프로그램의 상태를 변경하지 않는다. 함수 외부 스코프의 그 어떠한 변수의 값도 바꾸지 않으며, 임의의 타 객체를 생성하지 않는다.

예를 들어 함수가 console.log()를 내부에 갖고 있다면, 이 작업은 함수에 Output을 내는 것과는 관계가 없는 별도의 작업이므로 순수함수라고 볼 수 없다.
또한 함수가 전역 변수를 사용해 함수 호출로 전역 변수의 값이 바뀐다면, 이 또한 순수함수라고 볼 수 없다.


순수함수를 만드는 기법이 내장 메서드를 사용하는 것보다 다형성에서 장점이 있다. = 활용도가 높다.


2. 고차 함수(Higher-order Function)를 사용할 수 있다.

고차 함수란, 함수를 일종의 객체로 취급하는 방식으로, 함수 내에 함수가 있는 구조를 만들 수 있는 기반이 된다.

고차 함수의 대표적인 종류로는 map(), filter(), reduce() 등이 있다.

💥 고차함수(Higher-order Function)란 아래의 조건들을 만족하는 함수:

  • 함수에 함수를 파라미터로 전달할 수 있다.
  • 함수의 반환값으로 함수를 사용할 수 있다.



3. 반복하지 않는다(Non-iterable).

for문이나 while문을 사용하는 대신, map, filter, reduce 등의 고차함수를 사용한다.
이들 고차함수는 반복문에서 수행하는 작업을 해줄 뿐만 아니라 함수도 인풋으로 받을 수 있다.


4. 값을 변경하지 않는다(Immutable Data).



5. 영속 자료 구조(Persistent Data Structure)를 사용한다.

효율적인 데이터 저장 및 활용을 위해서 Data Sharing 방식을 사용할 수 있는데,
이것은 기존 버전과 새 버전을 부분적으로 공유해 무언가를 추가, 변경, 이동하는 작업에서 불필요하게 데이터를 전부 복사하는 대신,
변경되는 부분만 새롭게 만들고 이전 부분들을 재사용 하는 방법이 된다.

많은 함수형 프로그래밍 언어에는 트리 자료 구조(trie data structures)(“트리”라고 발음)라고 불리는 특수한 불변의 자료 구조가 있다. 이는 객체의 계층에서 속성의 수준에 관계없이 속성이 변경될 수 없음을 의미한다.

Trie는 연산자에 의해 복사된 후 변경되지 않은 객체의 모든 부분에 대한 메모리 위치를 공유하기 위해 구조적인 공유(structural sharing) 를 사용한다. 이는 메모리를 덜 사용하고, 일부 작업에 대해 상당한 성능 향상을 가능하게 한다.

https://peter-cho.gitbook.io/book/10/persistent-data-structures
https://sungjk.github.io/2017/07/17/fp.html


커링(Currying)

커링(Currying):
함수에 인자를 하나씩 적용해 나가다가, 필요한 인자가 모두 채워지면 함수 본체를 실행하는 기법.

JavaScript는 커링이 지원되지 않지만, 1급 함수가 지원되고 reduce와 같은 고차함수를 사용할 수가 있기 때문에 커링과 같은 기법을 구현할 수가 있다.

🔎 여기서 잠깐, 1급 함수란...?
JavaScript에서 함수는 "1급 객체(First Class Citizen)", 다음의 조건을 만족하는 객체는 1급 객체라고 한다:
1) 변수나 데이터 구조안에 담을 수 있다.
2) 파라미터로 전달할 수 있다.
3) 반환값으로 사용할 수 있다.
4) 할당에 사용된 이름과 관계없이 고유한 구별이 가능하다.
5) 동적으로 프로퍼티 할당이 가능하다.

  • JavaScript에서 함수(Function)는 객체(Object)이므로 "1급 함수"로 불린다.

커리함수는 인자로 함수를 받고, 실행하는 즉시 함수를 리턴한다.



클로저(Closure)

클로저란?

클로저란,
함수가 속한 렉시컬 스코프를 기억하여 함수가 렉시컬 스코프 밖에서 실행될 때에도 이 스코프에 접근할 수 있게 하는 기능을 뜻한다.

예를 들어, 아래 코드에서


foo()를 실행하면 무엇이 출력될까?
답은 2가 나온다.

함수 baz()foo()의 렉시컬 스코프에 접근할 수 있고, baz()함수 자체를 값으로 넘긴다. 이 경우에 함수 baz()는 함수가 선언된 렉시컬 스코프 밖에서 실행됐다.

이것이 가능한 이유는baz()가 선언된 foo()의 내부 스코프는 여전히 baz()에 의해 '사용 중' 이므로 Garbage Collector에 의해 해제되지 않기 때문이다.

선언된 위치 덕에 baz()foo()스코프에 대한 렉시컬 스코프 클로저를 가지고, foo()baz()가 나중에 참조할 수 있도록 스코프를 살려준다. 즉, baz()는 여전히 해당 스코프에 대한 참조를 가지는데, 그 참조를 바로 클로저라고 부른다.

클로저는 호출된 함수가 원래 선언된 렉시컬 스코프에 계속해서 접근할 수 있도록 허용한다.
어떤 방식이든 함수를 값으로 넘겨 다른 위치에서 호출하는 행위는 모두 클로저가 작용한 예다.

  • 출처: 카일 심슨, 『YOU DON'T KNOW JS』, 한빛미디어(2017)

클로저를 사용하는 또 다른 예제로는 add_maker함수가 있다.

add_maker함수를 살펴보면, 7번줄에서 add_maker(30)를 실행하면서 매개변수로 넘겨준 30은 변수a에 담기게된다.
그리고 add_maker(30)를 실행한 결과는 function(b) { return a + b; }가 되어 변수 add30에 담기게 된다.
add30에 담긴 이 함수는 위 lexical scope의 a변수의 값을 참조(기억)하는 클로저가 된다.

add_maker는 함수를 리턴값으로 받는 함수이기도 하기 때문에 클로저일급함수의 개념을 갖고 있는 예제가 된다.


대표적 함수형 프로그래밍 라이브러리

아래 목록은 함수 컴비네이터(combinators)라고도 불리는 JavaScript의 대표적 라이브러리들이다.


Reference 🔍

*본 포스팅은 아래 서적/사이트들을 참고 및 인용하여 작성되었습니다.
학습단계로 잘못된 정보가 있을 수 있습니다. 잘못된 부분에 대해 알려주시면 곧바로 정정하도록 하겠습니다 😊

https://developer.qustory.com/post/programming-paradigm/
https://sungjk.github.io/2017/07/17/fp.html
https://www.inflearn.com/course/%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D/lecture/6779?tab=curriculum&speed=1.5
조영호, 『오브젝트』, 유키북스(2019)
카일 심슨, 『YOU DON'T KNOW JS』, 한빛미디어(2017)

profile
프린이의 코묻은 코드가 쌓이는 공간

0개의 댓글