fender
20k
2020-08-03 07:20:41 작성 2020-08-03 09:55:02 수정됨
20
2023

이런 식으로 코딩하는 게 의미가 있을까요? (반응형/함수형 실험)


요즘 조금 실험적인 걸 만들어보고 있습니다. 개념은 대략 Rx의 Observable을 일반 클래스 속성처럼 사용하게 해보자는 건데, 방금 작성한 테스트 코드 모양은 이렇습니다 (파이썬으로 작성했습니다):

def test_map_combinators(self):
class Fixture:
value = from_value(1)

doubled = map_value(value)(ops.map(lambda v: v * 2))

numbers = combine(value, doubled)(lambda o: rx.combine_latest(*o))

combined = combine_latest(value, doubled)(ops.map(lambda v: f"{v[0]} * 2 = {v[1]}"))

zipped = zip_values(value, doubled)(ops.map(lambda v: f"{v[0]} * 2 = {v[1]}"))

combined = []
zipped = []

fixture = Fixture()

observe(fixture.zipped).subscribe(zipped.append)
observe(fixture.combined).subscribe(combined.append)

self.assertEqual(1, fixture.value)
self.assertEqual(2, fixture.doubled)
self.assertEqual((1, 2), fixture.numbers)
self.assertEqual("1 * 2 = 2", fixture.combined)
self.assertEqual("1 * 2 = 2", fixture.zipped)
self.assertEqual(["1 * 2 = 2"], combined)
self.assertEqual(["1 * 2 = 2"], zipped)

fixture.value = 3

self.assertEqual(3, fixture.value)
self.assertEqual(6, fixture.doubled)
self.assertEqual((3, 6), fixture.numbers)
self.assertEqual("3 * 2 = 6", fixture.combined)
self.assertEqual("3 * 2 = 6", fixture.zipped)
self.assertEqual(["1 * 2 = 2", "3 * 2 = 2", "3 * 2 = 6"], combined)
self.assertEqual(["1 * 2 = 2", "3 * 2 = 6"], zipped)


이런 방식으로 복잡한 프로그램을 만드는 게 가능/바람직할까요?

부연하자면, Rx 자체가 아니라 위의 예시처럼 Rx를 클래스 속성 처럼 사용하는 것을 이야기하는 것입니다.

만들고는 있는데 아직은 저도 반신반의하네요.

0
  • 댓글 20

  • fender
    20k
    2020-08-03 07:23:58

    여담이지만, 파이썬의 타입 힌트 지원과 람다는 둘 다 꽤 후진 것 같습니다. 근데 둘이 합쳐놓으니 환장할 콜라보가 되더군요 -ㅅ-

  • 꽃중년보넥스
    -1k
    2020-08-03 07:50:29

    Rx를 잘 몰라서 안보려다가

    펜더님의 글이라 유심히 보다보니 무슨 말인지 예상은 되네요.


    C++로 치자면 아래 구문이 실행되는 것이 아니라 콜백함수로 꼽혔다가

    doubled = map_value(value)(ops.map(lambda v: v * 2))

    a.doubled = 10 처럼 입력시에는 반응하지 않다가

    b = a.doubled 처럼 출력시에는 꼽힌 콜백함수가 동작하는 원리인가 보군요.

    필요할 때만 일하게 하자? ㅎ


    사용성에 대한 것은

    좀 더 "구체적인 상황"을 만들어 봐야 알 것 같습니다.


    -3
  • fender
    20k
    2020-08-03 07:55:39 작성 2020-08-03 07:57:24 수정됨

    코딩요정바람돌이 // 의견 감사드립니다. Rx는 콜백과는 좀 개념이 다르고, 위의 코드는 Rx를 객체지향 맥락에서 좀 더 자연스럽게 사용해 보려는 시도라고 이해하시면 될 것 같습니다.

  • 꽃중년보넥스
    -1k
    2020-08-03 08:02:19

    연립방정식이라는 객체모델을 만들어 본 적이 있습니다.

    https://github.com/BonexGoo/Boss2D/blob/master/Boss2D/element/boss_solver.hpp

    비슷한 상황인지는 모르겠으나 계산공식으로 예시를 만들어 놓으셔서 함 썰을 풉니다.


    Solver A;

    A.Link("group_a", "a");

    A.Parse("b + (b + 100) * b");

    A.Execute();

    TRACE("%d", A.result().ToInteger()); // 0이 출력


    Solver B;

    B.Link("group_a", "b");

    B.Parse("200");

    B.Execute(true);

    TRACE("%d", A.result().ToInteger()); // 60200이 출력


    -3
  • fender
    20k
    2020-08-03 08:04:54
  • 꽃중년보넥스
    -1k
    2020-08-03 08:16:25

    제가 만든거랑 비슷한 개념인거 같은데요?

    다음번엔 OperatorType을 플러그인형태로 만들어 봐야겠다는 생각이 드네요.

    제가 만든 객체도 가장 중요한 것은 파서가 아니라 업데이트 체인입니다.

    변화된 내용에 따른 필요한 연쇄반응을 일으키는 매커니즘입니다.


    -3
  • 꽃중년보넥스
    -1k
    2020-08-03 08:19:21

    저도 옵저버랑 체인이라는 단어를 쓸 수 밖에 없어서 그랬는데..

    다들 저런 상황이면 옵저버, 체인이라는 어색한 단어를 쓸 수 밖에 없나보군요.

    -3
  • fender
    20k
    2020-08-03 08:25:07 작성 2020-08-03 08:28:24 수정됨

    코딩요정바람돌이 // 음... 그래도 의견 주셔서 고맙게 들으려고 했는데... 공부 좀 하세요, 제발.

  • 꽃중년보넥스
    -1k
    2020-08-03 09:08:23

    전 공부는 안해요~ㅎ

    닥친 일을 하는거지요. 훈련만 합니다.

    공부따윌 왜 합니까? 다 상품에 불과한 건데..

    -4
  • ggawa4030
    262
    2020-08-03 09:26:27

    아는척... 동문서답 ㅋㅋㅋ

    오늘도 웃고 갑니다 ㅎㅎㅎ

    제발공부좀 하세요 공감가네요 ㅋㅋㅋ

  • 인사동
    1k
    2020-08-03 09:39:10

    코딩요정바람돌이

    c++ 진영에선 RxCpp 가 유명하죠

    https://github.com/ReactiveX/RxCpp


    python 이라는걸 적어주시면 관심있는 분들이 더 잘 봐주실거라 생각합니다.

    subscribe랑 observe만 있다고 RX인지 아닌지는 잘 모르겠네요

    내공이 부족해서..

  • fender
    20k
    2020-08-03 09:56:31 작성 2020-08-03 09:59:08 수정됨

    인사동 // 감사합니다. 본문에 파이썬으로 작성했음을 명시했습니다. subscribe는 짐작하시는 바대로 Rx의 Observable.subscribe이고, observe는 제가 만든 라이브러리에서 'Rx 속성'을 Observable로 변환하는 기능을 합니다.

    쉽게 말해서 Rx의 일부 기능을 흉내내는 것이 아니라 Rx의 Observable로도 사용할 수 있는 OOP의 클래스 속성을 정의하는 방법이라고 정리할 수 있을 것 같습니다.

  • 내누알
    584
    2020-08-03 10:10:01 작성 2020-08-03 10:12:41 수정됨

    제가 해석해본 바로는, Rx 연산자의 반환 값을 class의 속성으로 바로 불러올수 있게 하고 싶다는거라고 읽었는데 맞나요?

    하자고 한다면 만드신것처럼 불가능하지는 않을텐데 rx 코딩을 하는 입장으로서는 조금 헷갈릴것 같아요.

    rx의 연산자의 역할은 이벤트의 흐름을 제어한다는 생각을 갖고 있고 만약 값을 갖고 있을 필요가 있다면

    BehaviorSubject 같은 인스턴스에 그 목적에 맞는 변수명을 붙여서 갖고 있어야 다른곳에서 사용할때 헷갈리지 않고 사용할수 있지 않을까 하는생각이들어요.


  • fender
    20k
    2020-08-03 10:21:42 작성 2020-08-03 10:28:33 수정됨

    내누알 // 네, 사실 뜯어보면 내부에는 비해이비어 서브젝트가 있습니다 ㅎㅎ; 정확하게는 상태값을 직접 관리하는 '속성'과 임의의 옵저버블을 래핑하는 '뷰'가 있는데, 전자의 구현이 서브젝트 기반으로 되어 있습니다.

    그럼 그냥 서브젝트를 쓰면 되지 않나라는 생각이 들 수 있는데, 주된 목적이 객체지향적 설계에서 속성의 개념을 대체하려는 것이기 때문에 일반 옵저버블로 하기 어려운 부분이 있기 때문입니다.

    예를들어, Rx의 예시로 자주 등장하는 트윗 같은 걸 스트림으로 받아서 조작을 하겠다는 것이라면 Rx로 충분하겠지만, 만일 게임의 캐릭터나 아이템 같은 개념을 객체지향으로 설계하면서 모든 속성을 다 옵저버블로 만들겠다면 이래저래 복잡해지기 때문입니다.

    (예 - 캐릭터의 기본 체력을 레벨 속성으로 부터 도출했다면, 이는 비해이비어 서브젝트가 아닌 옵저버블이기 때문에 현재값을 알기 위해선 보일러플레이트 코드가 필요함.)

    그래서 클래스 구조로 설계를 하면서도, 속성은 부모나 다른 객체의 속성으로 부터 선언적으로 구성할 수 있고, 별도의 이벤트 핸들러 없이 바로 Rx 파이프라인으로 변환 가능하면서도 기존 객체지향 처럼 바로 값으로도 직접 참조하고 변경 가능하게 해보자는 것이 의도였습니다.

  • fender
    20k
    2020-08-03 10:37:14

    여담인데 적고 보니 'reactive'를 '반응형'이라고 했군요. 보통 'responsive'를 그렇게 부르는 것 같던데, 혹시 전자에 해당하는 많이 쓰는 우리말 용어다 있을까요?

  • 내누알
    584
    2020-08-03 10:40:24 작성 2020-08-03 10:41:02 수정됨

    get에서는 observable을 반환하고 set에서는 값을 넣어주는 기능을 하는 클래스가 상상이 되네요 ㅎㅎ;

    rxSwift를 하는 입장에서 말씀하신 부분을 구현한다면 inner class나 private class등으로 말씀하신 부분에 대해 기능 구현하고 swift의 extension과 computed property를 이용해서 변수화시켜 반환시킨다면 비슷하게 구현할수 있을거 같아요 ㅎㅎ;

    rxpython쪽에 그 기능을 한번 PR하시고 리뷰 받아보시는 것도 좋을것 같네요 ㅎㅎ

    편리한 프로퍼티라면 언젠가 rxswift쪽에도 누군가 구현을 해주지 않을까 하는 기대를 가져봅니다

  • fender
    20k
    2020-08-03 10:49:31 작성 2020-08-03 11:17:39 수정됨

    내누알 // 정확게는 get에선 옵저버블의 현재값을 반환하고 observe(속성)을 하면 옵저버블 자체가 반환이 됩니다:

    • Fixture.value -> ReactiveValue[T] 반환
    • fixture.value -> T 반환
    • observe(fixture.value) -> Observable[T] 반환... 해야되겠지만 RxPy는 제너릭이 완전하지 않아서 일단 Observable -ㅅ-;


    얼마전 파이썬을 배우면서 '디스크립터'라는 개념을 보고 전자 같은게 가능하지 않을까 아이디어를 얻었습니다 ㅎㅎ;

    사실 오늘 예제를 소개한 것이 본격적으로 프로젝트를 홍보하기 전에 혹시 이게 말이 안되는 개념은 아닐까 싶어서 확인하고 싶어서였습니다.

    만일 이게 유용할 수 있는 개념이라면 일단은 Rx 자체를 확장하기 보단 - 현재 Rx는 공통 개념에 대한 여러 언어의 구현체라는 개념이기 때문에 개념 자체를 바꾸긴 힘들 겁니다 - 별도의 라이브러리로 만들고 레딧이나 디스코드 등에서 소소하게 홍보하면서 반응을 볼 생각이었습니다.

    좋은 의견 감사드립니다. 다른 분들도 혹시 예제에서 이런 부분은 좀 이상하다던지 그런 내용이 있다면 편하게 지적 부탁드리겠습니다.

    (왠지 하마님이 이런 쪽은 제일 고수이실 것 같은데 요즘 바쁘신지 잘 안보이시네요 ㅎㅎ)

  • 엡실론
    1k
    2020-08-03 12:59:55

    저는 rx는 서너번 정도 사용해봐서 자세히는 모릅니다만, 리뷰는 초보자의 의견도 중요하다 들어 적어봅니다.

    자바진영쪽 사람이라서 그런지 몰라도 Observable의 값을 이렇게 쉽게(?) 얻을 수 있는게 좀 위험해 보이네요. rx stream 밖에서 Observable의 값을 올바르게 이용하는 use case가 있는가도 조금 의문입니다.

    언급하신 기본 체력이 속성에서 도출되는 예에서도 속성이 변경될때마다 변경된 체력값을 얻어와서 화면이나 어딘가 업데이트를 해야 할텐데, 어차피 observable에 map같은 걸 써야하지, 그렇지 않고 현재값을 알 필요가 있을까 생각합니다.

    그리고, 위의 예제는 모두 계산이니 별 상관없지만, rx stream내에 async io 같은게 있는 경우에는, value를 변경한다고 바로 최종값이 변경되지도 않을 테고, 저 같은 초보자는 observable로 처리해야 할 부분을 현재값을 가져와서 오류를 만들어 낼 것 같은 예감이 드네요.

  • fender
    20k
    2020-08-03 13:28:37 작성 2020-08-03 13:36:26 수정됨

    엡실론 // 네, 저도 그게 가장 애매하다고 느꼈던 부분입니다. 다만, 개인적으로 함수형의 가장 보편적인 장점은 동시성에 대한 안전을 보장하는 것보단 선언적인 방식으로 코드를 조합(compose)할 수 있다는 점이라고 생각합니다.

    물론 전자의 특성이 매우 중요한 분야가 있는 건 맞지만, 예컨대 일반적 데스크탑 응용프로그램이나 게임엔진, 엑터 시스템 등 위에서 돌아가는 프로그램이라면 대부분의 경우 그런 문제를 원천적으로 배제할 수 있기도 합니다.

    반면에 예컨대 객체 지향으로 게임을 만드는 데 변하는 상태를 표현하는 모든 속성을 옵저버블로 대체한다면 보일러 플레이트 코드는 엄청나게 늘어나서 생산성이나 가독성을 떨어뜨릴 수 있습니다.

    예를들어 게임 엔진을 전제로 말한다면 캐릭터의 상태를 읽어서 화면을 갱신하는 작업 도중에 비동기적으로 값이 바뀌어 문제가 된다던지 하는 건 사실상 불가능합니다.

    하지만 이 경우에도 말씀처럼 화면 어딘가에 캐릭터의 체력을 보여주는 레이블을 선언적 방식으로 정의할 수 있다는 건 여전히 상당한 장점일 수 있다고 봅니다.

    특히 제안하는 방법은 외부의 실제 비동기적인 데이터(예 - 트윗)를 다루는 방법이 아니라 객체지향의 속성 개념을 대체하는 것이기 때문에, 기본적으로 근원을 따라 올라가면 어딘가의 클래스가 관리하는 서브젝트에 다다를 가능성이 높습니다.

    이 경우 어차피 기존 방식이라면 임의로 누구나 어느 스레드에서나 변경할 수 있는 변수 값으로 처리했을 내용이기에, 제시한 방식을 도입했을 때 없던 위험이 추가되진 않는다고 봅니다.

    참고로 현재 구현에선 옵저버블의 값이 없는 경우 이를 접근하려 하면 AttributeError를 발생시키고 있습니다. 시스템의 일부가 정말로 비동기 이벤트를 발생시킨다면 그런 부분만 그냥 일반적인 옵저버블 처럼 사용하면 되지 않나 정도로 생각하고 있습니다.

    좋은 의견 감사드립니다.

  • 마니
    2k
    2020-08-04 01:48:57 작성 2020-08-04 01:49:49 수정됨

    오우 파이썬 람다는 진짜 거지같네요....

    js를 주력으로 사용해서 그런진 몰라도 (swift 제외하곤) 람다식이 다 별로네요..

  • 로그인을 하시면 댓글을 등록할 수 있습니다.