brunch

매거진 ReactiveX

You can make anything
by writing

C.S.Lewis

by Tilltue Aug 09. 2020

반응형 프로그래밍이란 무엇인가?

Erik meijer, React 2014

이 글은 에릭 메이어의 2014년 React 컨퍼런스에서 발표 영상을 좀더 이해하기 쉽게 정리해본 내용이다.

[유튜브 영상링크] What does it mean to be reactive?



이 글을 쓰게된 이유


iOS 개발자로 RxSwift 를 약 5년째 사용하고 있지만, 반응형 프로그래밍을 누군가에게 명확하게 설명할 정도로 이해하고 있지는 않다고 생각했다.

약 4년전 이 발표를 이해하면 반응형 프로그래밍을 이해할 수 있다는 이야기를 보고,

이 발표를 수 차례, 몇 년간 보았지만, 이제서야 정리할 수 있을 것 같아 비로소 글을 쓰게 되었다.


- 사실 발표를 준비하고 친구에게 발표를 선행해봤는데, 수학적인 내용들이 좀 어렵다고 느껴지고 집중하기 힘들다는 피드백을 받아서 글로 정리했다.



What does it mean to be reactive?

이 발표를 한 에릭메이어는 누구인가?


Eric Meijer


Microsoft 에서 2013년까지 소프트웨어 아키텍트로 근무


2013년 - Applied Duality Inc. 를 설립,

2015년 - 페이스북에서 엔지니어링 디렉터로 근무


에릭의 연구에는 함수형 프로그래밍(특히 Haskell ) 컴파일러 구현, 프로그래밍 언어 디자인 등이 있음.


MS에서는 C#, Visual Basic, LINQ 그리고

Reactive programing framework (.NET) 개발에 관여


MS에서 Rx  만든 조직에 있던 사람







1. 인터넷으로 알아보기


에릭은 먼저 인터넷으로 살펴보자고 한다.

에릭은 리액티브 선언문에는 많은 버즈워드 들이 있고, 위키도 본질을 이야기하지는 않는다고 한다.

(2014 년보다 위키는 더 많은 내용이 추가된것 같지만?)


[리액티브 선언문] : https://www.reactivemanifesto.org/ko

[반응형 프로그래밍 위키] : https://en.wikipedia.org/wiki/Reactive_programming

이외에 stackoverflow, 블로그 등에서 이야기될 때에도 반응형 프로그래밍에 대한 많은 혼란이 있다.


에릭은 사람들이 코드 없이 반응형 프로그래밍에 대해서 이야기 한다고 한다.

( 어떤 이는 코드에 대해 이야기 하면서도 코드는 작성하지 않은 채로 이야기 하기도 하고 )


보통 위키의 구절을 몇몇 인용하고, 엑셀을 예로 들며 값의 변화에 반응해서 결과값을 보여주는 것 등등으로 반응형 프로그래밍을 설명 하곤 한다. 이것도 하나의 설명이 될 수는 있겠지만, 명확하게 반응형 프로그래밍을 정의, 이해하는데에는 별로 도움이 되지 않는다.


2. 레시 렘포트의 조언을 따라 수학적으로 접근해보자

( 오직 수학만으로 하는것은 아님 :D )


Leslie Lamport

- 2013 튜링상 수상


래시 램포트의 수상이후 3가지 질답중 하나


"코딩하기 전에 하려는 작업을 이해해야 합니다.

우리가 사양이라고 하는 청사진없이 다리나 집을 짓고자 한다면 신뢰할수 없을 것입니다.

이것은 사양(명세)의 사용을 방해하는 소프트웨어의 문화에 대한 이야기입니다.


우리는 지난 몇천년 동안 개발에 대해 정확하게 묘사할수 있는 “수학"이라는 훌륭한 방법을 가지고 있습니다.


우리가 만드는 것에 대한 생각의 방법으로 수학을 사용해야한다고 생각합니다."



에릭은 우리는 프로그래머니까 이 조언에 따라 반응형 프로그래밍 아이디어에 대해서, 오랬동안 증명에 사용된 방법인 수학으로 설명해보자고 한다.


* 참고 자료: 레시 렘포트 수상이후 3가지 질답에 대한 원문

https://www.technologyreview.com/2014/03/18/173641/three-questions-for-leslie-lamport-winner-of-computings-top-prize/




- 4가지 기본적인 사이드 이펙트

표에 보면 동기와 비동기가 있다.


동기는 함수 블럭 에서 동기적으로 결과를 받는다. 함수블럭이 아닌 여러가지 표현이 있을수 있겠지만 이해하기 어렵지 않은,  우리가 오랬동안 (현재도) 프로그래밍에서 사용하고 있는 개념이다.


T 는 하나에 대한 이야기이고, Enumerable 은 값이 연속적으로 있는 것을 나타낸다.


비동기는 새로운, 모던한 형태의 것이다. ( 나중에 일어나는 것 )

퓨처는 함수 블럭에서 값을 얻는 것이 아닌 나중에 값이 준비 되었을때 알려주는 것이다

퓨처는 하나의 값에 대한 것이고 옵저버블은 Enuerable 처럼 계속 값을 전달받는 것이다.


우리가 쓰는 많은 결과들은 비동기 적인 것들이 많다. 이것은 비동기적인 데이터 스트림이다.

이제 이 사이드 이펙트들에 대해 수학적으로 접근해 본다.



3. Objects 의 사이드 이펙트


Objects are just Getters And Setters


오브젝트에는 Setter와 Getter가 있다. 이것으로 사이드 이펙트를 만든다 상태를 변경하면서,

이 것을 시작점으로 Setter와 Getter의 본질에 대해 생각해보자.


Getter 의 본질

Getter 는 함수이고 아무 것도 주지 않아도 값을 가져온다. 이 함수는 레이지 프로듀서다.

스스로는 아무것도 하지 않고, 콜을 했을때 비로소 값을 전달한다.

자판기에 비유할수 있다.

버튼을 눌러야 비로소 음료를 준다.


Covariant (공변성)

A 가 B의 서브타입일 때 A 를 반환하는 Getter는 서브타입인 B를 반환하는 Getter가 될 수 있다.

앞서 예를 들었던 것처럼 자판기를 생각해보자.

B가 음료, A가 코카콜라 일때  코카콜라를 제공하는 자판기는 음료를 제공하는 자판기가 될 수 있다.



Functor ( Getter )

A에서 B를 변환 함수로 이 것을 또 다른 함수로 올린다.( 기본적으로 펑터는 리프트 성질을 가지고 있음 )

A Getter 를 매핑해서 또 다른 펑션을 가져오도록 ( 여기서는 B Getter )  할 수 있다.

이게 Functor의 전부다.


부연 설명 ( 이해되지 않아도 무방 )

아래 map f a ... 는 Scala로 작성한 타입 표현식이다. 이 식이 이해가 되지 않아도 무방하다.
설명해 보자면,  A Getter 에서 B Getter 를 가져와야 하고 이걸 정의하려 한다.
- f ( 함수: function )  는 A => B 의 기능을 수행하는 함수다.
- a 는 A의 Getter 이다.
() => f( a() ) 는 Getter B 와 같은 의미 이다!  f( a() ) 를 통하면 B 유형을 얻을수 있으니까

결론:
map f a = () => f( a() ) 는 Getter A 로 Getter B를 가져오는 맵 함수에 대해 타입으로 바라본 표현식


Getter 의 Side Effect - Type Signature

( 예외와 종료를 고려 )


예외와 종료를 고려한 사이드 이펙트의 좀 더 명확한 표현식이다.


Getter는 예외를 발생할수 있고, 예외가 없이 값을 주거나 종료 될수도 있다.



Getter를 사용할때 우려되는 여러 타입들에 대해 명확한 표현식들을 살펴봤다.

우리가 코드를 작성할때 Getter를 사용한다면, 사이드 이펙트를 생각할때 항상 주의해야 하는 것들이다.


여기까지 sync 의 T 에 대한 수학적인 정리이다.



Getter 의 Getter

Getter 가 필요할땐 어떻게 해야할까?

Getter 를 반환하는 Getter 가 있으면 된다.


이러한 역활을 수행하는 Getter Getter 는 이렇게 표현할수 있다. 실패하지 않고 항상 Getter 를 반환해준다.



이 개념을 Enumerable 으로 생각할수 있다.  

계속 값을 받고 싶은 상황, Getter 를 반환하는 Getter를 사용한다.



Getter Gettter 의 Interface

getEnumerator() 는 getter getter 이고 아무것도 받지 않고 Enumerator 를 반환합니다.

Enumerator 는 다음에 값이 있음을 알려주거나, 값을 사용할수 있다.

우리가 항상 사용하고 있는 콜렉션과 같다.

예외가 있거나, 혹은 moveNext 는 값이 있거나 없음을 알리고, current 에 현재 값이 있다.



Lift 함수

Enumerator[T] -> Enumerator[S] 로 변환하는 f 를 가지고,

Enumerator[T] 를 f를 통해 Enumerator[S] 로 변환하고, Enumerable[S] 를 얻는다






Functor ( Getter Getter )

Getter Getter의 Functor 도 Getter 의 Functor 와 크게 다르지 않다.

A => B로 변환을 가지고 Enumerable[A] 로 Enumerable[B] 를 map 한다.



여기서의 f 도 A => B 를 의미한다.

- as 는 Enumerable[A]

- 앞서 설명한 lift 함수 와 map 을 가지고 표현식을 구성한다.


아무것도 하지 않고 기본 정의를 가지고 lifting 만 수행했는데, 낮은 타입에서 더 높은 레벨로 올렸다.


여기까지 sync 의 Enumerable 에 대한 수학적인 정리이다.




4. 지금까지 했던 것들에서 화살표를 뒤집어 보자.


Setter 의 본질

Setter 는 값을 주지만 아무것도 하지 않는다.


Setter는 쓰레기통에 비유할 수 있다.

쓰레기통은 쓰레기를 던지면 그냥 담긴다.

암시적인 side effect 를 가지고 있는 IO이다..




Contravariant (반공변성)


A 가 B의 서브타입일때

B의 Setter 는 A의 Setter의 하위타입이다.




coFunctor ( contravariant funtor)

coFunctor 는 functor 와 기본적 원리는 같다.


여기의 map은 contravatiant map 이다.

A 를 B 로 변환하는 식을 가지고,

B의 setter를 A의 setter로 변환하는 map 입니다.



a 는 ( A=>()  A Setter 의 타입를 나타냄) b( ) 는 유닛 즉 B 의 Setter를 수행한 것.

f ( a ) 는 setter A 의 변환이므로 setter B 가 된다.


map f a 는 b 의 셋터를 통해 a의 셋터를 얻는것에 대한 표현식입니다.


중요한건 타입의 변형을 반복하는 것만으로 coFuntor 를 얻어냈다는 사실이다.

타입이 ( 그리고 타입의 변환 )이 옳기에 명백하게 옳은 변형이다.


Setter 의 Side Effect - Type Signature

( 예외와 종료를 고려 )

예외와 종료를 고려한 사이드 이펙트의 좀 더 명확한 표현식이다.


Setter는 예외가 발생될 수 있고,

예외가 없이 값을 넣거나 종료 될수 있다.


우측에도 암시적인 IO가 있을수 있지만, 여기서는 아무것도 하지 않고 있으니까, 좌측에 집중한다.


이 타입을 가지고 여러 작업을 할 수 있다. Scala 의 패턴 매칭을 생각해보면 3가지 기능을 수행할수 있다.

( switch case 문을 생각해도 좋다. )


값을 주거나, 종료 되거나, 에러를 반환하거나 이렇게 3가지가 있다.


Setter Setter

Setter 하는 Setter 를 생각하자.


쓰레기통이 있고, 쓰레기를 넣기위해 쓰레기통에 넣으면 다른 쓰레기통에 넣어지는 것 처럼.





Setter Setter 의 Interface

아래의 인터페이스 ... 익숙하지 않은가?? 마침내!! Observable 에 도달했다!

Setter Setter 의 인터페이스를 보자. Enumerable 과 반대되는 형상이다.

수학적인 방법을 통해 타입의 변형과 사이드 이펙트를 정의 해가며, 마침내 여기에 도달했다.

여기서는 Rx의 개념으로 표현되어있지만, 우리는 사실 이걸 매일 사용하고 있다.


예를 들어 버튼타입의 이벤트가 있다고 했을때 여러 타입의 이벤트를 제공한다, onPressed, onClick, 등등.


여기서는 앞서 설명한 Setter의 사이드 이펙트 중, 예외가 발생하거나, 값을 받거나, 종료되거나, 이 이벤트들을 제공하는 것이다.

Observable 에서 subscribe 를 통해 옵저버 타입을 전달한다. 이벤트 콜백을 받을 객체를

Rx 를 사용하지 않더라도 우리가 항상 활용하고 있던 방법이다.


Lift 함수

Observer[T] -> Observer[S] 가 구현된 함수 f 를 가지고

covariant functor 의 성질로 Observable[T] 를 Observable[S] 로 변형하는 리프트 함수이다.


Functor

Functor 는 Enumerable 과 완진히 동일하다.


여기까지 async 의 Observable 에 대한 수학적인 정리이다.

(Future T 의 경우 싱글 타입의 Observable 이라 이해해도 무방하겠지. )




4가지 사이드 이펙트에 대해 수학적으로 접근해봤다.


지금까지 래시 램포트의 조언을 따라 심플한 Getter와 Setter로 시작해서 기본적인 사이드 이펙트에 대해서 수학적으로 설명해왔고, Observable 에 이르렀다. 그리고 Observable 의 변형또한 설명되었다.

Reactive 프로그래밍 아이디어에 도달했다.  


:D 씐나~



But! 여기서 끝나는 것이 아니다.


No Silver Bullet

프레드 브룩스 1986년 논문 "은 탄환은 없다" 발췌


소프트웨어의 본질은 데이터 세트, 데이터 항목간 관계 알고리즘, 함수 호출 처럼 서로 맞물리는 개념으로 이루어진 구조물이다. 표현방법은 여러가지로 달리 하더라고 개념적 구조물은 동일하다는 점에서 이 본질은 추상적이다. 추상적이라 해도 그 구조물은 고도로 정밀하며 풍부한 세부내용을 담고 있다.


소프트웨어를 만드는 데 있어 정말 어려운 부분은 개념적 구조물의 명세, 설계, 테스트 이다. 신택스 오류 같은 건 개념적 오류에 비하면 아무것도 아닌 문제나 다름없다.


이 전재가 참이라면 은탄환은 존재 할수 없다.

소프트웨어 개발은 원래 엄청 어려운 일인 것이다.



에릭은 프로그래밍을 할때 우리는 문제해결을 위해 사이드 이펙트를 어떤식으로 발생할지에 대해 고민하고, 결정해야 하며. 아무도 믿지 말아야 한다고 말했다.


예를 들어 만약 스트림이 무한대로 된다고 이미 알고있다고 하면,  (절대 예외가 발생하지 않고, 절대 끝나지 않는다고) 그럴때는 () => Future[A] 만 필요하다 try , option 이 필요없으니까.

인터페이스도 onNext 만 필요하다. completed 되지 않고, error 도 발생하지 않을 것이니까.


이처럼 상황에 맞춰 이렇게 사이드 이펙트의 타입을 선택하고, 조합을 만들면서 프로그래밍 되어야 한다.

필요 없을땐 버리기도 하고, 취하기도 하면서


이처럼 실제 프로그래밍에서 필요한 요구사항들을 채우기 위해서, 많은 Enumerable 의 구체화된 구현들 많은 변형들이 있고, Observable도 마찬가지로 많은 구현과 변형들이 존재한다.


Enumerable 은 Pull 기반이다. 반면 Observable 은 Push 기반이다.

Enumerable 은 값을 가지고 있고, moveNext를 통해 값을 Pulling 한다.

Observable 은 Push 기반이며 나중에 값이 전달된다.


Iterable 의 구체화된 구현들


Observable 의 구체화된 구현들


5. 다른 사이드 이펙트: 지연 ( Latency )

좀더 명백한 사이드 이펙트를 보자. 지연에 관해서.

enumerator 는 move next를 통해 값을 처리한다. 공급에 지연이 있다.

observable 은 값이 들어올때에 처리가 된다. 처리에 지연이 있다.


이런 부분은 어떻게 해결할수 있을까?



지연을 가진 Getter


Getter가 future 타입을 반환하는 형태


Future 는 값이 나중에 얻어지는 것,

즉 지연을 가진 사이드 이펙트 이다.



Interface : Async Iterable


이렇게 인터페이스를 구성하면, 값을 가져오는데 지연이 있을때 유용하게 사용할수 있다.





AsyncIterable 과 Observable 은 비슷해 보이지만,

이벤트 처리 주체( self vs observer )가 다르고, 사이드 이펙트의 성질이 다르다.


지연을 가진 Setter

또 다른 지연을 가진, Setter가 지연을 가진 형태다.

값을 주었을때 나중에 처리될수 있도록 한다.






Interface : Async Observable

값을 나중에 Setter가 지연을 가지고 처리되기때문에, Fast Producer를 보호한다.

기본적인 개념은 Async Iterable 과 같다.


에릭은 구현중이라고 표현했기 때문에 현재 사용할수 있는 형태의 것인지는 잘 모르겠다. ( 2014 년의 발표니까 구현되었을것 같긴하다. )


- 난 이걸 써본적은 없는것 같다. 구체적인 구현도 본적이 없는것 같고...


더 다양해진 선택지!

처음에는 4가지 형태의 사이드 이펙트가 있었지만, 이제 6가지가 되었다.

물론 더 많을수도있고 혼합할 수 있는 요소들도 더 있을지 모르겠다.

어쨋든 우린 이게 모두 필요하다고 생각한다.

어떤때는 latency 가 필요하기도 하고 , 필요하지 않기도 하다. 하나의 타입만 가지고 사용할 수는 없다.


마지막으로...


Agile is Dead

Long Live

Agility


- Dave thomas



데이브 토마스의 Agile is Dead, Long Live Agility 에 대해서 말해본다.


사람들이 Agile 단어를 남용하고, 마치 Agile 하게 일하자 라고 하면 그것이 되는 것처럼 사용했었던것을

비판하고, 민첩한 방법으로 무언가를 수행하려 하는 것이 본질이라는 이야기를 했다.


사실 아직도 Agile의 단어가 오용되는 것은 여전한 것 같다.


반응형이라는 말은 더이상 중요하지 않다.

그보다 중요한 것은 사이드 이펙트를 올바르게 구성하는 것에 있다.


프로그래밍을 하고, api 설계를 하고, 프로그래밍 언어를 만드는 모든 것 들의 주요 지점은 사이드 이펙트다.


반응형 프로그래밍, 대화형 프로그래밍, 함수형 프로그래밍, 명령형 프로그래밍, OOP, POP, 등등

단 하나로 구성되지 않아야 한다는게 에릭의 의견이다. 단 하나의 개념이 상위 개념이 아니라고 생각한다.


우리는 해커다.

우리는 올바른, 살아있는 코드를 작성해야 한다. 특정 믿음을 가지고 그것에만 의존해서, 마치 종교적으로 코드를 작성하지 말아야 한다.


Reactive is Dead, Long Live Composing Side Effects.



여기까지 에릭의 발표를 정리했다.

최대한 쉽게 적으려 하고, 간결하게 적어보려고 노력했지만... 그래도 길고 길었다.

여기까지 읽은 사람이 있을까? ㅎ_ㅎ;;


마지막으로 이 발표를 공부하면서 생각한 나의 의견도 적어보려고 한다.


What is a Hacker?


해커는, 상자 밖을 생각하는 사람이다.


해커는, 지적호기심에 대해 시스템의 한계를 실험하는 사람이다.


우리는 해커가 되어야 한다고 생각한다.


발췌:  https://www.schneier.com/blog/archives/2006/09/what_is_a_hacke.html



요즈음에도 맹목적으로 기술 트랜드를 쫓거나, 하나의 기술에 심취하거나, 특정 아키텍쳐를 맹신하거나 하는 프로그래머들이 많이 보이곤 한다.


나도 그랬었던적이 있고, 아직도 내가 그런 잘못을 하고 있지는 않은지 종종 뒤돌아보고 의심한다.

만약 그게 잘못되었다는 것을 알지 못한다면 계속 잘못된 상태에 있었겠지...

물론 가보지 않고는 모르기 때문에 뭐든 하지 않아야 한다는 이야기는 아니다. 그저 의심을 가지고 기술에 대한 호기심을 멈추지 말아야 된다고 생각한다.


에릭의 발표에는 온도차이는 있겠지만 우리 프로그래머들은, 이런 것들을 경계해야 한다고 이야기하고 싶어한다고 느꼈다. 그리고 주변에 이런 것을 경계해야 함을 전파해야 된다고 이야기 하는것으로 보였다.

내가 이 글을 쓴 또 다른 이유중에 하나이다.



부록: 관련한 나의 실패담


5년전 Swift 를 갓 접하고 곧이어 접하게된 RxSwift 는 정말 새로운 경험이었다. 마치 이 기술이면 모든 문제가 해결될 것 같이 보였다.

MVVM 아키텍쳐를 접했을때도 그랬다. 뭔가 멋져보였고, 몇몇 블로그 글들만 줏어 보고서 MVVM 과 Rx를 사용한 데이터 바인딩 따위를 몇개 구현해두고 MVVM 을 구현했다! 라면서 마치 뭔가 해낸것처럼 우쭐해했다.


그리고 맞은 팩-폭


이후 MVVM에 대해 더 깊이 공부하게 되었고, RxSwift 에 대해서도 더 깊게 공부하게 되었다. 그러면서 테스트를 접하고, TDD를 접하게 됐다.


일깨워준 누군가로 인해 더 성장할수 있었기에 나도 부족한 글이지만 이를 누군가에게 전하기 위해 나름의 노력을 하고 있다.


마침.



브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari