React 와 함수형 프로그래밍 Functional Programming vs OOP

반응형

아래 글은 잘못된 판단. 개념 파악을 위한 도움용으로 참고하면 좋음.

이 글에 관련 내용 기재. https://thewavelet.tistory.com/76

 

즉, React 는 우선적으로 함수형 프로그래밍 방식을 기본으로 사용하고 또한 Javascript 기반으로 하는 Node.js 또한 그렇게 하는 것이 좋아보인다. React 가 함수형 프로그래밍 방식을 가장 잘 적용한 라이브러리라고 보아도 좋을 듯 하다.

 

명령형 프로그래밍의 if, for, i++ 과 같은 것 보다는 간단하고 틀리지 않을 filter, map, reduce, take, groupBy 와 같은 작은 단위의 순수 함수들을 프로그래밍의 기본 구성 요소로 간주하면서 프로그래밍 하는 사고와 방식.

 

함수형 프로그래밍 : 1930년대 알론소 처치의 람다 계산법 부터 시작됨. 명령형 프로그래밍과 비교될 수 있다.

명령형 프로그래밍은 프로그램을 명령의 수행으로 보며 어떻게에 초점을 맞춘 튜링 머신으로 부터 출발한 C, Java 같은 것.

함수형 프로그래밍은 프로그램을 함수의 계산으로 보며 무엇에 초점을 맞춘 람다 계산식으로 부터 출발한 Haskell 같은 것.

 

- 함수가 1급 객체 / 1급 시민

- 불변성 (Immutability)

- 순수함수 (Pure function)

- 고차 함수 (High-Order Function)

- 합성 함수 (Function composition)

- 선언형 (추상화가 높아지고 직관적이 된다. 사람이 이해하기 편하다.)

 

함수형 프로그래밍 방식과 객체 지향 프로그래밍 방식이 대비되어 비교될 수 있는 개념이기는 하지만 완전히 반대되는 개념은 아니다.

 

함수형 프로그래밍 방식은 불변성과 순수함수를 지키고 최대한 선언형 방식의 프로그램이 되도록 지켜야할 규칙을 따르는 것이다.

 

함수형 프로그래밍에서도 객체 지향 프로그래밍에서 나오는 3대 개념인 캡슐화, 추상화, 다형성이 필요하다면 사용된다.

 

객체 지향 프로그래밍 하면 클래스가 우선 떠오른다. 클래스로 하나로 묶어 캡슐화 하고 상속 등과 같이 다형성을 적용할 수 있으며 

프로퍼티, 메소드 와 같이 상태와 행위를 가진 객체로서 추상화 시킬 수 있다. 클래스로 부터 생성된 객체 간의 유기적인 상호 작용을 통해 로직을 구성하는 방식이 객체 지향 프로그래밍이다. 가장 대표적인 것이 Java 언어라고 볼 수 있다. main 함수 자체도 클래스 안에 존재하지 않는가?

 

객체 지향 프로그래밍 방식으로 개발을 하다 보면 프로퍼티 와 메서드를 정의하고 interface(protocol) 또는 클래스의 상속 등을 통해 다형성을 적용하여 추상화를 통해 프로그램을 구현한다. 그러나 또한 메서드의 구현은 명령형으로 구현된다. 이렇게 객체 지향적 추상성에만 집중을 하다 보면 다수의 동시 다발적인 비동기 이벤트들의 처리에 대한 문제 해결에 많은 오류를 유발하게 될 수 있다. 대표적인 예로 User Interface 의 경우 웹페이지에 요소 요소 별로 다량의 데이터가 실시간으로 비동기적으로 많은 사용자 이벤트와 함께 처리되는 경우를 들 수 있다. ios 나 android 의 경우 전통적 MVC 패턴으로 개발을 진행하게 되는데 이럴 경우 보통 쓰레드를 통해 이러한 문제를 다룬다. 전통적인 MVC 와 쓰레드를 통한 개발을 할 경우 초반에는 명령형 방식으로 개발에 편의가 있을 수는 있지만 차후 각 캡슐화된 컴포넌트 단위의 상태의 변화에 다양한 외부 부수 효과가 예기치 않게 영향을 미쳐 관리가 어렵고 오류를 다수 유발할 수 있다. 하물며 테스팅을 하더라도 말이다. 객체지향형 프로그래밍에서 추상화라는 개념이 나와서 함수형 프로그래밍에는 추상화가 되지 않는다는 것은 아니다. React 의 jsx 문법은 선언형으로 매우 직관적으로 사람이 인식하기 좋은 고도로 추상화된 것이다. 단지 객체지향 프로그래밍에서의 추상화는 프로퍼티, 메서드를 통해 객체를 정의하는데 이 방식이 함수형의 순수함수, 불변성, 부수효과 분리의 규칙을 전혀 고려하지 않고 있으며 또한 고려하기에도 부적합한 방식으로 되어 있기 때문에 이러한 추상화를 말하는 것이다. React 의 jsx 와 같이 함수형 프로그래밍의 개발방식은 선언적이므로 오히려 말 뜻 그대로의 추상화는 함수형이 더욱 뛰어나다고 볼 수 있다.

 

안드로이드의 Thread 와 IOS 의 Grand Central Dispatch (쓰레드인지 이벤트룹인지? 쓰레드라고 한다. 이벤트 룹은 런루프라는 용어로 존재한다고 한다. 따라서 GCD 는 실제 일반적인 멀티쓰레드가 맞다고 한다.) 를 사용하여 UI 의 쓰레드와는 별개로 작업을 처리하고 그 결과를 또한 UI 쓰레드에 적절한 방법으로 안전하게 반영한다. 여기서 크게 두가지 혼동되는 개념이 또 등장한다. 바로 Thread 와 Coroutine 이다. 코루틴은 EventLoop 이다. kotlin 에서는 Lambda 를 사용하고 ios 에서는 Clojure 를 그리고 javascript 에서는 함수 자체를 이러한 코루틴을 구현할 때 사용하는 것들이라 볼 수 있다. 병렬 처리를 위한 병행 프로그래밍 즉, 멀티태스킹에 있어 선점형 / 비선점형 으로 구분될 수 있는 이러한 개념은 둘 모두 1930년대 부터 나왔던 개념이지만 그동안은 라운드 로빈이니 락이니 세마포어니 하는 개념의 쓰레드 기반의 선점형 (일정시간으로 동일한 간격으로 끊어 병렬적으로 처리. OS 에 의해 강제되니 선점되었다고 한다.) 방식만을 고려하여 발전해왔다. 하지만 UI 상에서 다양한 동시다발적 비동기 이벤트들에 대한 처리에 있어 이러한 전통적 방식은 객체지향적인 개발에 있어 추상화를 통해 해당 추상적인 로직 구현부에 어떻게 구현하는가에 집중하다 보니 Side Effect 들과 실제 해당 캡슐화된 컴포넌트 안의 상태 변화를 주는 부분과 분리하여 구현되지 않기 때문에 서버로 부터 데이터를 받아 갱신을 하다가도 오류로 인해 경고가 뜨다가도 또한 사용자가 버튼을 눌러 다른 부분의 상태가 변경되기도 하는 경우 이러한 동시다발적인 상황에서 많은 오류를 유발하게 되었다. 이런 동시다발적인 다수의 비동기 이벤트들에 의한 상태 변경 및 Side Effect 들을 적절히 오류없이 처리하기 위해서 함수형 프로그래밍 방식이 유용하게 사용되었고 이러한 함수형 프로그래밍의 구현을 위해 함수를 일급 시민으로 하는 실질적인 기술이 필요하였고 이것이 코루틴 즉, 함수 단위로 이벤트 루프 큐에 넣어 처리하는 방식이 추가되고 발전되었다. 사실 자바스크립트는 브라우저라는 애플리케이션 위에서 동작해야 했기에 javascript 인터프리터를 통한 간단한 앱 내장 자체 처리기가 필요해서 이벤트 루프 방식의 javascript 엔진이 만들어진 것일 수도 있다. 그리고 이것이 꽤나 괜찮은 방식 같아서 다른 곳에서도 자체적으로 이벤트루프 방식으로 동작하는 것을 추가한 듯 하다.

 

객체지향 프로그래밍 방식에서는 디자인 패턴이라 불리는 여러 대표적인 코딩 방식들이 있다. 이러한 방식들은 함수형 프로그래밍에서는 오히려 방해물이 될 수도 있을 수 있다. 왜냐하면 이러한 객체지향 관점에서 나온 디자인 패턴들은 위에서 언급한 함수형 방식을 고려하지 않은 방식이기 때문이다. 이 디자인 패턴들에 중점을 두고 그 방식대로 개발하다가는 함수형 프로그래밍을 해칠 수 있을 가능성이 높아진다. 그렇다고 저러한 객체지향에 기반을 둔 디자인 패턴이 나쁘다는 것이 아니다. 적어도 함수형 방식에서는 적절하지 않을 수 있다는 것이다. 물론 광범위한 디자인 패턴이란 개념은 함수형 프로그래밍에 있어서 자주 사용되는 코딩 방식도 디자인 패턴이라고 볼 수 있지만 여기서는 과거의 싱글턴, 데코레이터, 팩토리 등등과 같은 것들을 말한다.

 

하지만 클래스, 디자인 패턴과 같은 개념들이 함수형 프로그래밍에 있어 반대되는 개념은 아니다. 단지 새로운 프로그래밍 방식이며 기존의 객체지향 개발방식과 함께 사용될 수도 있다. 실제로 kotlin 이나 swift 같은 경우 그렇게 개발이 되며 필요에 따라 람다, 스트림이나 클로저 등을 통해 이런 방식을 부분 부분 적용한다. 함수형 프로그래밍은 명령형 프로그래밍 방식과 거의 동시대에 나온 개념이며 오히려 객체지향 프로그래밍 보다 더 오래 전에 나온 개념이다. 최근에 이에 대한 필요성이 부각되어 고도화 되어가고 있다.

 

ios 는 정확히 잘 모르겠다. 개인적으로는 ios 는 병렬 프로그래밍 처리에 있어 선점형인 쓰레드 방식도 사용하며 비선점형인 이벤트 루프도 사용하는 것 같다. 즉, 기본적으로는 선점형 멀티태스킹으로 처리되는 일반적인 OS 의 방식대로 하지만 또한 따로 구현된 이벤트 루프도 가지고 있어 비선점형 멀티태스킹 방식으로도 로직을 구성할 수 있는 것 같다. 아무튼 Apple 의 Mac, ios 의 swift 는 매우 유연하고 고차원적이지만 매우 어려운 언어이기도 한 듯 하다. Geek 한 언어인 것은 맞다. Objective C 는 더욱더 그러할 듯... 그래도 결국 Apple 의 개발 언어에서 사용된 개념들이 파생되어 다른 언어에도 적용되는 걸 보면 선두 주자임에는 확실하다. 

 

선점형은 OS 가 동일한 시간 간격으로 끊어서 여러 명령을 적절히 분배하여 강제로 선점하여 처리하는 방식을 말한다. 

비선점형은 자바스크립트의 콜백, 프로미스 처럼 함수 안의 함수 그리고 그 함수의 종료까지 모두 정의된 채로 이벤트 루프 큐에 들어가서 처리가 되며 그 함수 내부의 정의에 따라 처리 순서나 시간이 결정되기 때문에 OS 가 CPU 를 선점하고 있지 않으므로 비선점형이라 한다.

 

 

 

*** 과거 초반에 의문 사항 글을 적어놓음. 지금은 어느정도 정리되었음. 참고용. ***


https://itnext.io/react-class-components-are-dead-hint-not-yet-1d0a151173b8

아래의 이유로 React Hook 을 통한 FunctionComponent 를 사용하는 것이 좋다고 합니다.

class components have downsides;
Confusing (both human and machines, especially at binding and thiskeyword)
Lifecycle methods, logic spread over different lifecycle methods
Hard to test compared to functional components
Compiled code size and compile time


componentShouldUpdate 같은 곳에 각 props 를 비교해가면서 업데이트를 시킬 것인가 하는 로직을 짜보셨거나, 그래서 PureComponent 라는 것도 나왔었고 각 LifeCycle 메소드에 모든 로직을 집어넣었어야 했습니다. 그러다 보면 유지보수가 어려운 코드가 되었구요.

그리하여 이러한 부분들을 각각의 관심 로직에 맞게 분리해서 사용할 수 있도록 Hook 를 사용하는 것이 더 좋아 보이고 또한 무거운 구현체인 클래스컴포넌트 보다 훨씬 가벼워 보입니다.

하지만 이것은 ReactComponent 에 국한된 이야기가 아닌가 하는 생각이 듭니다. 분명 객체지향 프로그래밍 이라는 것이 매우 직관적이고 논리적이라 스케일이 커져가는 개발에 있어 이것 없이 개발하기 어려울 수 있다고 생각합니다.

단지 클래스 ReactComponent 가 LifeCycle 메소드 위주로 구현이 이미 되어 있기 때문에 그 만들어진 틀이 개발하는데 오히려 방해가 되었고 더 무겁기만 했기 때문에 ReactComponent 를 FunctionComponent 에 Hook 을 추가하여 가볍게 만들지 않았나 봅니다.

export interface Database {
    open(): Promise<SQLite.SQLiteDatabase>;
    close(): Promise<void>;
    // Create
    createList(newListTitle: string): Promise<void>;
    addListItem(text: string, list: List): Promise<void>;
    // Read
    getAllLists(): Promise<List[]>;
    getListItems(list: List, doneItemsLast: boolean): Promise<ListItem[]>;
    // Update
    updateListItem(listItem: ListItem): Promise<void>;
    // Delete
    deleteList(list: List): Promise<void>;
}



이런식으로 인터페이스 정의하고

class DatabaseImpl implements Database {}



이런식으로 구현하는 것이 모든 개발에 있어 정석인 객체지향 프로그래밍이 아닌가 생각합니다.

java 가 매우 인기 있었던 이유이기도 했었구요. Typescript 라는 마이크로소프트의 거대한 노가다 산물이 안정적인 단계에 들어서면서 이런 개발이 javascript 진영에서 가능해진 것 같습니다.

개인적으로 이런 의견을 적는 이유가 리액트의 FunctionComponent 를 보면서 그렇다면 class 와 interface 를 사용하는 객체지향프로그래밍이 자바스크립트 진영에서는 선호되지 않고 함수를 기반으로 개발을 하는 것이 좋은가? 라는 의문이 들어서였습니다.

class 도 결국 function 의 문법적 설탕이라고 알고 있습니다. 그래서 function 을 겹겹이 둘러싸서 만든 class 보다 순수하게 function 을 사용해서 코딩하는게 더 좋은 것인가? 라는 생각도 들었습니다.

결론은 리액트에서 컴포넌트를 함수형과 훅으로 하기로 방향을 튼 이유는 class 를 사용하는 것이 잘못된 방법이라서 그런 것이 아니라 리액트 컴포넌트 라는 것이 함수+훅 방식으로 사용할 때 더욱 깔끔하고 가볍기 때문이라서 그런듯 합니다.

다른 개발에 있어서는 필요한 경우 class 를 사용하여 객체지향으로 개발하는 것이 나쁜 방법이 아니라 원래 그렇게 해야하는 것이라고 개인적으로 결론을 내렸습니다.

 

 

 

 

 

리액트 FunctionComponent 도 함수형 프로그래밍의 방식을 적용한 것이 맞네요.
저는 단순히 함수형태로 가볍고 편리하게 하기 위한 목적이라 생각했었는데 이게 내부 state 에 대한 쓰레드 safe, 불변성 유지, 선언형 방식, side-effect 고려 등이 또한 필요했기 때문에 변경한 것 같습니다.
확실히 리액트 컴포넌트는 함수형 프로그래밍이 필요한 부분인 것 같습니다. 이곳 저곳에서 비동기적인 이벤트가 동시다발적으로 일어날 수 있으니깐요. 그래서 리액트의 컴포넌트는 상속보다 합성을 이용해야 하겠네요.

또 저런 기능적인 측면 말고도 코딩 자체에서도 object 타입구조를 바꿀 수 있다던가 hook 와 같이 선언형 프로그래밍으로 더 편리한 개발을 할 수 있다는 측면도 있을 수 있겠네요.

그런데 싱글턴 패턴이나 옵저버 패턴 과 같은 디자인 패턴 같은 것은 적용하기가 힘들어 보이는데 맞나요?

만약 데이터베이스 관련 로직을 작성해야 할 경우에는 트랜잭션이나 쓰레드 같은 것 때문에 함수형 프로그래밍의 상태의 불변성을 유지시켜주는 순수함수 개념도 큰 의미가 없을 수 있을 거 같네요.
하나의 DB 자원에 row 부분을 update 시킬 경우 순수함수로 작성되어 있다고 해서 해당 순수함수와 연결된 state 는 immutable 이 지켜지지만 DB 는 io 로 분리된 state 로 볼 수 있으니 이러한 경우에는 class 로 객체지향적으로 작성하는게 더 맞는 거 겠죠?

그래서 결론은 UI 단의 상태와 로직은 함수형 프로그래밍으로 하고 데이터베이스와 같이 어차피 state 나 자체적인 로직이 함수형이 아닐 경우의 자원과의 관련 로직은 객체지향 방식으로 작성하면 좋겠네요. 맞을까요?

사실 리액트 네이티브에서 SQLite 단 로직과 Dropbox 동기화 로직을 작성하려고 하는데 뭐가 올바른 방식 일까 하는 생각이 들어서 질문하게 되었습니다.



합성
https://reactjs-org-ko.netlify.com/docs/composition-vs-inheritance.html?fbclid=IwAR0NgjBlJ4qqPgYu5N9_4_I9VmpqGda3NhzPiMV_5jvXPhGYUcETdUwKPqo


함수형 프로그래밍
https://www.slideshare.net/ssusere9cffb/ss-84864214
https://umbum.dev/547
https://valuefactory.tistory.com/732
https://velog.io/@kyusung/함수형-프로그래밍-요약


Side Effects
Side effect는 반환 값 이외에, 호출 된 함수 밖에서 관찰할 수 있는 어플리케이션의 상태 변경이다.

 

 

반응형

댓글

Designed by JB FACTORY