리습사랑
481
2016-02-14 15:38:47
10
7884

디자인 패턴과 Clojure


4
4
  • 댓글 10

  • 정개발
    1k
    2016-02-14 20:21:26

    좋은 글 감사합니다.

    0
  • 하마
    6k
    2016-02-15 12:43:27

    커맨드 패턴의 역주를 보면

    "하지만 그것이 목적이라면 메소드 자체만을 전달하면 좋지 않을까? 왜 부질없이 객체를 다 전달하는가? 그저 우리는 그 객체의 특정 메소드를 호출하는 것이 관심일 뿐인데 말이다. 객체의 다른 부분들은 필요가 없는 것이다. 우리가 필요한 것은 객체가 아니라 함수다! 

    ...


    결국 단일 메소드 인터페이스는, 함수가 1급이 아니기 때문에, 1급인 객체에 함수를 매달아 전달하기 위한, 어쩔 수 없는 자바의 고육지책인 것이다."


    이렇게 주장합니다만 ,  이건 원본글의 한계 안에서 도출된 결과입니다.

    예를들어 C++ 나 Java 에서 커맨드패턴은 undo redo  구현시 많이 사용되곤 하는데 그때 커맨드객체는 내부에 execute 와 unexecute 를 가지며 중요한건 그 당시의 상태를 가지고 있습니다. 

    이러하듯 다양한 쓰임새가 있는데  ( POSA 1  참고

    자바는 쓸때없는 짓을 하고 있다라고 주장하는건 자바개발자가 보기엔  어리둥절 할 뿐이죠.

    원글에서 undo/redo 시 상태를, 객체는 존재하지 않는 클로져가 다루는 방식과  자바를 비교하는글이었더라면 하는 아쉬움이 있습니다. ( 뭐가 더 좋다식이 아닌 그냥 차이점)


    ps.

    이런 현장감넘치는 글 굉장히 좋아하며, 팁/테크로 가야 할 글 같습니다. 
    번역하신분들  고생하셨습니다. 감사합니다.




    네. 의견 감사합니다.

    원문 자체가 디자인 패턴을 클로저로 실제로 어떻게 완벽하게 구현하느냐 라는 측면보다는, 클로저와 자바와의 비교라는 측면에서 쓰여진 면이 많습니다. 하지만 적어도 에피소드를 곁들이면서 상당히 적은 코드로 나름 설득력 있게 설명을 이끌어가고 있다는 점에서 좀 높게 평가합니다만, 역시 좀 더 실질적이고 구체적인 코드를 원하시는 분들에게는 역시 좀...부족해 보일 수도 있으리라 봅니다. (사실 그래서 저희 역자끼리 완벽하게 돌아가는 예제로 다시 만들어 볼까... 하는 이야기도 있었습니다만... 역시 시간과 정력이...모자라서리...ㅜㅜ;;;)


    말씀하신 undo/redo시 상태를 저장 복원하는 것에 대해서는 아마도 메멘토 패턴을 보신다면 조금이나마 힌트를 얻으실까 싶습니다. 음... 그리고 뭐 케바케이겠지만 간단하게는 Closure를, 멀리스래딩 환경을 고려한다면 watch를 단 agent도 생각해 볼 수 있을 것 같구요. ^^;; (참고로, 클로저는 persistent data structure를 기본 내장하고 있어서 이런 구현이 매우 쉽다는...)


    역주에서의 요지는...


    어떤 하나의 엔터티(여기서는 커맨드 객체)가 함수전달 / 다형성 / 상태 라는 3가지 책임을 지는 것이 과연 옳은 것이냐라는, 단순히 디자인 패턴이 아닌 언어적 차원의 의문을 던지는 것으로 이해해 주시면 고맙겠습니다.  ^^;

    객체지향의 제 1 원칙인 단일책임원칙(SRP)을 생각해보면...한 엔터티가 여러가지 다양한 쓰임새를 지닌다는 것은...이건 왠지 이 원칙에 어긋하는 것은 아닌가...하는...


    저의 개인적인 생각입니다만...

    객체지향의 원칙인 SRP를 극한으로 밀어부치면 객체는 쪼개진다: 1급으로서의 상태와 함수로.

    뭐... 이런 생각을 해봅니다...^^;;;




    저는 위에 소개한 글을 번역한 사람 중의 한 사람이고, 현재 Clojure로 프로그래밍을 하고

    있습니다.


    위의 글을 번역하면서 제가 느낀 점은, 객체 지향의 디자인 패턴이라는 것이 함수형 언어에서는

    패턴이라고 부를 수 없을 정도로 간단하게 구현된다는 점이었습니다. 코드가 단순하니 복잡도는

    자연스럽게 줄어들게 됩니다. 다시 말해, 함수를 일급 객체(first class object)로 지원하면,

    클래스의 결합과 같은 복잡한 과정을 거치지 않고도 원하는 일을 간단하게 수행할 수 있다는

    점이 윗글을 쓴 사람이 전하고자 하는 글의 요지라고 생각합니다.


    그리고 말씀하신 커맨드 패턴에서의 상태 관리는, 함수형 언어에서도 가능합니다. 상태 패턴이나

    메멘토 패턴 예제에서 그 내용을 확인하실 수 있습니다.


    객체 지향 언어에서는 클래스 내의 멤버 변수 안에 상태를 가두어 놓고 관리하지요. 함수형

    언어에서는 그와는 반대로 상태가 필요한 경우, 상태를 함수의 외부에 둡니다. 절차 지향이나

    객체 지향 언어에서는 전역 변수의 사용을 금하지만, 함수형 언어에서는 오히려 이를

    장려합니다. mutable이 아닌 immutable value를 기본으로 하기 때문에 가능해진 일입니다.


    아시다시피, 함수형 언어에서는 상태의 변경을 최소화하고자 합니다. 그 이유는 코드의

    재사용성을 높이고자 하는 것이고요. 가장 이상적인 프로그래밍 모델은 기존의 코드를 단순히

    조합하는 것만으로도 원하는 프로그램을 작성하는 것이지요. 그런데 함수가 상태를 변경하게

    되면, 함수 간의 조립도(composability)가 떨어지고, 코드의 복잡도가 상승합니다. 일례로

    Clojure에는 웹 프레임워크라는 것이 따로 존재하지 않습니다. 그 이유는 함수형 프로그래밍이

    제공하는 함수 간 조립성이 뛰어나기 때문에, 자신이 원하는 라이브러리들을 조합하는 것만으로도

    웹 프로그래밍을 하는 것이 가능해지기 때문입니다.


    아래의 그림을 통해 함수형 프로그래밍의 모델을 설명해보면, 먼저 함수의 외부에 있는 상태

    변수 s에서 값을 하나 가져 옵니다. 아래의 연속적인 코드 호출에서 처음과 끝에 있는 함수 A와

    F만 상태 변수를 읽고 쓰게 되고 그 나머지 함수 B, C, D, E는 상태를 변경하지 않으면서 필요한

    값을 구해 나갑니다. 이렇게 해서 얻어진 최종값을 함수 F가 상태 변수 s에 다시 쓰게 됩니다.

    이렇게 되면, 상태를 직접 읽고 쓰는 함수의 수를 줄일 수 있게 됩니다.


    s --> A --> B --> C --> D --> E --> F --> s


    객체지향 언어에서는 위의 함수 A, B, C, D, E, F 모두가 상태 변수 s에 접근해 직접 변경을

    시도합니다. 이렇게 되면 이 함수들은 상태의 공유로 인해 서로 간에 의존성이 생기게 되서,

    다른 코드에서 이 함수들을 재사용하기 힘들어 지죠.  아울러 코드가 복잡해지고 디버깅하기도

    더 힘들어집니다. 




     

    이 포스트는 클로저의 능력을 강조하기 위한 글이라는것 충분히 알기 때문에,  추가로 언급하신 함수형의 장점에 대해 굳이 사족 ( 진짜 함수형이 쉬운가라든지, 진짜 객체가 안좋은가 ,  대규모 개발에 어울리는가 등등 ) 은 달지 않겠습니다. 


    persistent data structure 는 참고해보겠습니다.  감사합니다.

    * 저도  클로저에 대한 호기심이 있는 편입니다. :-D  (제 번역글 : 함수형 프로그래밍 과 부작용 ) 

    0
  • 하마
    6k
    2016-02-15 13:43:41

    클로저 대중화를 위해서 저 패턴들 하나하나를 더 길게 풀고 , 클로저 문법 설명 자세히 해서 시리즈로 하나씩 올리면 or 책으로 저술하거나 하면  대박 칠거 같습니다.   (  전 무지해서..무임승차를 ㅜㅜ  ) 사실 저런 훑어보는 내용은 너무 어렵기 때문에 클로저가 먼지 모르는 사람이거나, 잠깐 살펴본 사람이라면 그냥 지나칠수 밖에 없는지라. 

    1
  • 지붕뚫고높이차
    747
    2016-02-15 18:41:22

    예제중 싱글톤 패턴만 관심있게 봤는데요

    1. 쓸모없는 코드를 나열해 복잡하게 만든 느낌

    2. 객체와 변수(기본데이터형) 를 같은 개념으로 이해한 오류

    4. java 컴파일러의 특성을 이해 못한 final 사용

    5. 가상머신(jvm) 이 알아서 원자성을 보장해 주는데 

    쓸때없는 Double-Checked Locking 이나 synchronized 의 불편함을 강조한다음


    함수형 언어로 3줄만에 구현되니 함수형 언어가 더 단순하다고 하는건

    그리 공감되지 않습니다.


    제가 보기에는 이브나 패드로나 둘다 바보에요.


    그리고 함수형 언어의 장점으로 이야기한 아래 모듈간 배치는
    s --> A --> B --> C --> D --> E --> F --> s

    s 를 전역변수로 선언 안하면

    java 에서도 훌륭하게 구현 가능할것 같은데 제가 잘못 알고 있는걸까요? ^^


    0
  • Jet Li
    102
    2016-02-16 00:42:23

    지붕뚫고높이차님이 말씀하신 내용에 동감합니다. 저런식으로 싱글톤 패턴을 clojure에

    일대일 매칭으로 변환 하는것은  그냥 표현만 달리하는 것일뿐  전역 변수를  선언해서

    상태를 나타내도록 사용하는 것으로서 본질적으로  차이가 없습니다.


    이러한 전역 상태 변수는  프로그램을 복잡하게 하는 요소입니다. 결론 적으로

    이러한 문제에서 clojure의 해결법은 전역 상태변수를 사용하는 함수를

    지역 값을 사용하는 함수, 말하자면 순수 함수 형태로 바꿔서 사용하라는 것입니다.


    단편적 디자인 패턴1:1매칭으로  xxx보다 이런면에서 더 좋다라고 이야기한다면

    xxx의 사용자에게 반감만 불러 일으킬 뿐이라고 생각합니다. clojure가 지향하는 바가 무엇인가

    왜 만들게 되었나를 느껴 보시려면 Rich Hickey - Simple Made Easy 를 추천드립니다.




    0
  • 구르마
    315
    2016-02-16 03:24:58

    @Jet Li

    일반적으로 전역변수는 나쁘다고 알려져 왔습니다. 

    Global Values Are Bad


    하지만 반드시 그런 것은 아닙니다. 다음 링크에서는 다른 주장을 합니다.

    Local state is harmful


    데이타베이스는 일종의 거대한 전역 변수(혹은 싱글톤?)입니다.


    실제로 페이스북의 React를 랩핑한 클로저스크립트 라이브러리 reagent를 이용한 re-frame SPA 웹앱 프레임웍은 모든 상태를 담는 단 하나의 전역변수인 app-db를 둡니다.


    전역이냐 지역이냐의 문제는 절대적인 것이 아니며, 중요한 것은 [상태]라는 복잡성을 해결하기 위해 해당 문제 영역에서 적절한 방식을 채택하는 것이 아닐까 생각합니다.


    그리고 순수함수는 부수효과가 없는 함수를 말하는 것이지, 지역값을 갖느냐의 문제와는 전혀 상관없습니다.


    ps. 참고로 함수형 프로그래밍과 객체지향 언어의 디자인 패턴 구현을 1:1로 비교한 것은 이 글이 처음은 아닙니다.

    http://norvig.com/design-patterns/design-patterns.pdf



    0
  • Jet Li
    102
    2016-02-16 06:20:21

    약간의 오해가 있는것 같습니다.  용어를 좀더 명확하게 하자면

    말씀하신 local state는 제가 말한 지역 값(local immutable value)과 다릅니다.


    데이터베이스의 내부구현이 비지니스 로직 어플리케이션에 드러나는 경우가 있나요?

    프로그래머 입장에서 DB는 아키텍쳐에서 바운더리가 다른것 아닌가요?


    reagent가 무엇인지 살짝 훓어 보았습니다. 상태 사용을 권장하는 UI라이브러네요. 

    사용자 프로그래머가 SPA의 상태를 어떻게 편하게 다룰 수 있도록 하는지 궁금해집니다.

    한번 자세히 들여다 봐야겠습니다.





    0
  • 리습사랑
    481
    2016-02-16 10:41:27

    먼저 제가 올린 게시글에 답글을 달아 주신 모든 분들에게 감사를 드립니다. 그런데 저희들이 위의 글을 번역하고 이곳에 소개하고자 한 본래 의도(함수가 일급 객체가 되면, 디자인 패턴의 구현이 아주 단순해진다는 사실을 소개)와는 약간 다른 방향으로 논의가 진행되는 점이 아쉽기는 합니다만, 이런 논의의 장이 프로그래밍에 대한 진지한 고민을 할 기회를 제공하고, 이렇게 의견을 교환하는 과정에서 자신의 생각이 정리되고 때로는 교정되는 기회가 된다는 점에서 환영합니다. 누가 누구를 이긴다는 차원이 아니라, 결과적으로 프로그래밍에 대한 이해를 깊게 해 준다는 점에서 이러한 논의가 서로에게 도움이 되는 장이 되었으면 합니다. 다음은 저의 생각입니다.


    ----------------------------------------------------------------------


    상태가 전혀 없는 프로그래밍이 과연 가능할까요? 함수형 언어는 상태를 '최소화'하려는 노력의 결과물이지, 상태를 '전무'하게 만들고자 하는 시도는 아니지요. '어쩔 수 없이' 상태를 관리해야만 하는 상황에서 사용하라고 있는 것이, 클로저에서는 atom, ref, agent, var 자료형입니다.


    현실적으로 순수 함수만으로 프로그래밍을 할 수는 없습니다. 그 이유는 나 자신부터가 순수(?)하지 못하고, 이 세상 자체도 순수하지 못한 것으로 가득 차 있기 때문입니다. 그것을 프로그래밍 언어는 어쨋든 처리할 수 있는 도구를 제공해야만 합니다. 함수형 언어라고 예외는 아니지요.


    저는 예전에, 상태와 관련된 모든 값들을 DB에 모두 때려 박아 놓고, 그 값을 조회, 변경하는 일과 같은 것을 DB에 모두 떠넘겨 버리면, 프로그래밍이 좀 더 수월해질텐데 하는 생각을 막연히 한 적이 있었습니다. 하지만, 그 생각이 현실적이지 못했던 이유는 메모리 access 속도와 DB access 속도가 현격하게 차이가 났기 때문입니다. 그런데 클로저에서의 atom이나, ref 같은 자료형은 그 자체가 하나의 DB처럼 작동합니다. STM(Software Transactional Memory)이라는 말에서도 짐작할 수 있듯이, DB의 transaction을 메모리 상으로 옮겨온 것으로 보면 됩니다. 즉 메모리 DB로 사용 가능하다는 이야기입니다. 게다가 검색 속도도 엄청 빠르고(예를 들면, map 자료형 같은 경우는, 실제로는 balanced 32진 트리로 구현되어 있어, 하나의 map이 가질 수 있는 최대 요소의 개수가 2^32개인데, 이 개수가 다 찬다해도 최대 깊이가 7인 트리밖에 안됩니다), 상태의 변경에 들어가는 부하도 persistent data structure 덕분에 상당히 적습니다.


    real DB에 값을 읽고 변경하는 것은 안전한 일이고, 메모리 DB에 같은 작업을 하는 것은 위험한 일이다라고 생각하는 것은 모순된 반응입니다. 개발자들 중 누구도 real DB에 값을 쓰고 변경하는 것을 위험천만한 일이라고 생각하지 않습니다. 실제로 하나의 atom을 메모리 DB처럼 관리하려고 하는 시도가 DataScript와 re-frame 같은 라이브러리들입니다.


    함수형 언어에서도 상태의 관리는 여전히 성가신 일이고 어려운 일입니다. 그것을 하나의 전역 변수에서 모두 관리하겠다고 하는 발상을 처음 접했을 때, 당시에는 저도 위의 분들과 같은 의문이 들었습니다만, atom을 일종의 메모리 DB로 이해하면서부터, 그리고 구루마님의 말씀처럼 기존에 있던 real DB를 하나의 거대한 전역 변수의 일종으로 이해하게 되면, 한 개의 atom을 이용해 프로그램의 모든 상태를 관리하는 모델이, 훌륭한 상태 관리 모델이 될 수 있곘다는 판단이 들었습니다(상태 변수들이 프로그램의 이곳 저곳에 흩어져 있는 것보다는 확실히 관리하기 편해집니다). 기존의 통념과는 아주 다른 접근법입니다만, 그때 제가 얻은 결론은, 천재적인 프로그래머들은 통념에 얽매이지 않고 본질을 꿰뚫어 본 후 그 해결책을 제시하는 사람들이구나 하는 것이었습니다.

    0
  • 구르마
    315
    2016-02-16 11:11:59

    @Jet Li

    | 약간의 오해가 있는것 같습니다.  용어를 좀더 명확하게 하자면

    | 말씀하신 local state는 제가 말한 지역 값(local immutable value)과 다릅니다.

    싱글톤은 local state이지, local immutable value는 아니죠. 갑자기 왜 immutable value가 나오는지 이해할 수 없으나 immutable value는 항상 안전합니다. local이든 global이든.


    | 데이터베이스의 내부구현이 비지니스 로직 어플리케이션에 드러나는 경우가 있나요?

    | 프로그래머 입장에서 DB는 아키텍쳐에서 바운더리가 다른것 아닌가요?

    음... 맞는 말씀입니다만...

    제 의도는...  웹 어프리케이션중 DB없이 돌아가는 것이 없으니... 전체적으로 본다면 DB도 하나의 어플리케이션의 구성품으로 볼 수 있고, 그렇다면 이것은 그 접근이 단순 대입/참조에서 SQL로 확장된 거대 전역 변수로 상상해 볼 수 있지 않느냐는 것으로 이해해 주시면 고맙겠습니다. ^^;;;


    | reagent가 무엇인지 살짝 훓어 보았습니다. 상태 사용을 권장하는 UI라이브러네요. 

    전역 변수도 많이 사용합니다. ^^;

    ratom이라는 전역변수로 만들어 사용합니다.


    | 사용자 프로그래머가 SPA의 상태를 어떻게 편하게 다룰 수 있도록 하는지 궁금해집니다.

    | 한번 자세히 들여다 봐야겠습니다.

    네, 추천드립니다.

    전 패러다임의 엄청난 충격을 받았습니다. ^^


    0
  • elomn
    10
    2016-07-12 16:49:33

    구르마님의 글들을 찾다보니 여기까지 오게 됐네요. Clojure를 좋아하는 학생으로서 이런 분들이 늘어났으면 좋겠습니다. 너무나 좋은 글들 잘 봤습니다.


    Reagent의 경우, 자바스크립트(ReactJS)에서의 Redux 패턴이 생각나네요. Redux 패턴 역시 변수의 값을 직접적으로 재할당하여 바꾸는(State Change) 것을 강력한 안티 패턴으로 규정하고 있고, 그 대신에 액션이라는 객체(명령)를 통해 간접적으로 스토어(앱의 모든 상태를 관리하는 단 하나의 객체 트리)를 변경시키는 수 밖에 없습니다.


    이를 (state, action) => state(당연하게도 state는 언제나 새로운 객체로 반환됨) 형식의 하나의 리듀서(reducer)라고 표현하는데, 저는 마치 이게 '모든 변화의 기록을 한 장부에서 관리'하는 것처럼 느껴집니다.


    때로는 이러한 재연산(기존의 객체를 바꾸기보다 새로운 객체를 반환하는 것)이 너무 값이 비싸지 않냐고 할텐데, React의 Virtual DOM이나 Om같은 클로저스크립트 라이브러리들이 증명했듯이 (값이 불변한다는 안정적인 그늘에서) 전체적인 재연산을 현명하게 피하고, 어떤것들만이 변화되었는지 추적할 수 있다면 정말로! 나은 성능과 코드의 복잡성을 크게 줄일 수 있게 됩니다.

    Angular 같은 경우에도 값을 직접적으로 바꾸기보다 변화된 부분만을 새로 반환해나가는 방식을 채택하고 있고, 이제는 현대 트렌드에서도 비주류라고 하기에 민망한 방식이 되었습니다.


    아무튼, 이 장부를 통해 Redux에서는 [시간 여행]이라는 재미난 일을 가능하게 하는데, 언제 어디서나 자연스럽게 애플리케이션의 상태를 뒤로(또는 원하는 곳으로) 되돌릴 수 있다는 겁니다. 왜냐하면 하나의 장부가 모든 상태의 변화(액션들)를 기록하고 있으니까요.


    Elm이라는 언어에 와서는 이런 방식을 언어 차원에서 아예 강제해버리는데, 다만 리듀서가 아니라 업데이터(updater)라는 이름(하는 일은 똑같습니다)의 "모델 뷰 업데이트" 아키텍처라고 표현하고 있습니다. 덕분에 Elm에서 작성하는 모든 프로그램(메모장부터 시작해서, 게임, D3같은 시각표현)은 필연적으로 시간 여행이 가능해지는 재미난 현상이 일어납니다. 

    Redux 한글 문서

    Change-detection-in-angular-2

    Elm Blog, time travel made easy


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