2017년 1월 26일 목요일

[clojure] 클로저 소개글

사내 블로그를 적어야 할 일이 있어서(할당량...) 무엇을 적을 까 하다가
클로저에 대해 한 번 써보았다.
좀 약파는 글이기는 한데 재미있었다.
http://blog.naver.com/andwise/220920969200

2017년 1월 22일 일요일

[clojure][design pattern] 상태패턴 , 비지터 패턴


clojure design pattern


출처::http://clojure.or.kr/docs/clojure-and-gof-design-patterns.html#command

3. 상태패턴

유료/무료 사용자 차이, 잔액 계산, 유료 전환

JAVA 
1.두개의상태를 enum으로 만든다.
public enum UserState {
  SUBSCRIPTION(Integer.MAX_VALUE), NO_SUBSCRIPTION(10);

  private int newsLimit; 
 
  UserState(int newsLimit) { this.newsLimit = newsLimit; }
  public int getNewsLimit() { return newsLimit; }
}
2. 유저를 만든다.
public class User {
  private int money = 0;
  private UserState state = UserState.NO_SUBSCRIPTION;
  private final static int SUBSCRIPTION_COST = 30;

  public List newsFeed() { // enum안에 들어있는 리미트 값으로 보여준다.
    return DB.getNews(state.getNewsLimit());
  }

  public void pay(int money) { //돈을 넣어서 잔액을 지불하고 임계점을 넘으면 유료회원이 된다.
    this.money += money;
    if (state == UserState.NO_SUBSCRIPTION
        && this.money >= SUBSCRIPTION_COST) {
      // buy subscription
      state = UserState.SUBSCRIPTION;
      this.money -= SUBSCRIPTION_COST;
    }
  }
}
CLOJURE multimethod
 
(defmulti news-feed :user-state)

(defmethod news-feed :subscription [user]
  (db/news-feed))

(defmethod news-feed :no-subscription [user]
  (take 10 (db/news-feed))) 
** defmulti는 무엇을 플래그로 멀티메소드를 만들 것인가. (:user-state으로 분기한다. 

(def user (atom {:name "Jackie Brown"
                 :balance 0
                 :user-state :no-subscription}))

(def ^:const SUBSCRIPTION_COST 30)

(defn pay [user amount]
  (swap! user update-in [:balance] + amount)
  (when (and (>= (:balance @user) SUBSCRIPTION_COST)
             (= :no-subscription (:user-state @user)))
    (swap! user assoc :user-state :subscription)
    (swap! user update-in [:balance] - SUBSCRIPTION_COST)))

(news-feed @user) ;; top 10
(pay user 10)
(news-feed @user) ;; top 10
(pay user 25)
(news-feed @user) ;; all news

 4. 방문자 패턴 (Visitor pattern);
public abstract class Item{ abstract void accept(Visitor v); }

class Message extends Item { void accept(Visitor v) { v.visit(this);} }
class Activity extends Item  { void accept(Visitor v) { v.visit(this);} }
// 비지터
public interface visitor {
  void visit(Activity a);
  void visit(Message m);
}

class PDFVisitor implements Visitor {
  public void visit(Activity a) { System.out.println("PDFExporter.export(a) - Activity");
  public void visit(Message m) { System.out.println("PDFExporter.export(a) - Message");
}
class ExcelVisitor implements Visitor {
  public void visit(Activity a) { System.out.println("ExcelExporter.export(a) - Activity");
  public void visit(Message m) { System.out.println("ExcelExporter.export(a) - Message");
}
main {
  Item i = new Message();
  Visitor v = new PDFVisitor();
  i.accept(v);
}

더블디스패치패턴이 생겨난 이유는(추측컨데) 인자 타입체크를 하지 않기 때문이다. 예를들어
(비지터 패턴을 사용하지 않는 경우 가정 출처링크 참고)
Item i = new Message();
i.accept()를 실행하면 그것이 Activity인지 Message인지 알 필요가 없다. 왜냐하면 이놈은 타입체크가 이루어지기 때문이다. 하지만 아래 놈은 다르다.
Format f = new PDF(); //여기서 Format은 PDF,EXCEL,GIF를 모두 다루는 놈이라 가정하자.
i.accept(f)
될거라 생각하지 말자.
Format f는 인자로 들어와서 타입체크가 되지 않았다. (인자는 타입체크를 하지 않는다는 것이 중요하다.) 그래서 더블디스패치로 이중 타입체크를 하는 것이다. 하는 방법은 아주 단순하다.
i.apply(f); 
// 만약 이런 메소드를 실행하려 하는데 i는 타입체크가 되었고 f는 인자이기 때문에 타입체크가 안되있다면 이 메소드를 바로 실행하는 것이 아니다.
f.apply(i); // 이렇게 바꿔서 f도 타입체크를 하도록 하는 것이다. 그러면 두번 타입체크가 되는 것이다.
어쨋든 Visitor패턴으로 잘 만들었다.

clojure

(defmulti export
  (fn [item format] [(:type item) format]))
{:type :message :content "Say what again!"}
{:type :message, :content "Say what again!"}
{:type :activity :content "Quoting Ezekiel 25:17"}
{:type :activity, :content "Quoting Ezekiel 25:17"}

(defmethod export [:activity :pdf] [item format]
 (println "exporter/activity->pdf" item))

(defmethod export [:activity :xml] [item format]
  (println "exporter/activity->xml" item))
(defmethod export [:message :pdf] [item format]
  (println "exporter/message->pdf" item))
(defmethod export [:message :xml] [item format]
  (println "exporter/message->xml" item))

(defmethod export :default [item format]
  (throw (IllegalArgumentException. "not supported")))
===============================================
(export {:type :message } :xml)
exporter/message->xml {:type :message}
nil
user=> (export {:type :message } :pdf)
exporter/message->pdf {:type :message}
===============================================
(하지만 :pdf:xml 사이에는 아무런 상하 관계(hierarchy)가 존재하지 않네요. 단순히 키워드일 뿐이잖아요?)
hierarchy만들기
(derive ::pdf ::format)
(derive ::xml ::format)
(isa? ::xml ::format)
(isa? ::pdf ::format)

(defmethod export [:message ::pdf] [item format] (println ":message ::pdf" item))
(defmethod export [:message ::format] [item format] (println ":message ::FORMAT!!!!" item))

(export {:type :message :content "SAYHIHIHIHI"} ::pdf)
:message ::pdf {:type :message, :content SAYHIHIHIHI}
nil
user=> (export {:type :message :content "???????????"} ::xml)
:message ::FORMAT!!!! {:type :message, :content ???????????}
nil

::xml을 구현하지도 않았는데 만들어졌다. FORMAT으로 거슬러 올라가서 일이 실행된것!!

** 더블디스페치를 지원하면 비지터패턴은 의미가 없다.




[clojure][design pattern] 클로저 디자인패턴 - 전략패턴

clojure design pattern
출처::http://clojure.or.kr/docs/clojure-and-gof-design-patterns.html#command

2. 전략패턴(Strategy Pattern)

이름순으로 정렬(단, 유료고객은 먼저)

(def users [{:subscription true :name "Y"} {:subscription true :name "Z"} {:subscription false :name "Z"}])

(sort (comparator (fn [u1 u2]
                    (cond
                      (= (:subscription u1) (:subscription u2))
                      (neg? (compare (:name u1) (:name u2)))

                      (:subscription u1)
                      true

                      :else
                      false)))
      users)
위 소스는 comparator를 만들고 하는 것이 아주 비슷하다.
아래 소스를 봐보자.
(sort-by (juxt (complement :subscription) :name) users) ;; forward sort
(sort-by (juxt :subscription :name) #(compare %2 %1) users) ;; reverse sort
워워 하나씩 보자.
(sort-by count ["aaa" "bb" "c"])

("c" "bb" "aaa")
juxt 이녀석은 뭘까? (juxtaposition : 병렬, 병치)
((juxt a b c) x) == [(a x) (b x) (c x)]
(for [user users]
  ((juxt :subscription :name) user))
;; ([true "Y"] [true "Z"] [false "Z"])


(for [user users]
  ((juxt (complement :subscription) :name) user))
;; ([false "Y"] [false "Z"] [true "Z"])

complement (보수 말하는건가) : 함수를 받아서 true/false의 반대값을 내보냄
(def odd?? (complement even?))
(odd?? 3)
true 
이걸 먼저 알아보자
(sort-by (juxt (complement :subscription) :name) users) ;; forward sort
user=> ((complement :subscription ) {:subscription "HI"})
false
user=> ((complement :a ) {:b "HI"})
true

두 줄로 만들어지는 것이 신기하다. juxt는 엄청난 함수 인 것 같다. 나중에 쓸일이 많을 것 같다.

컴파일 00

컴파일 과정
1. 구문 해석
2. 의미 해석
3. 중간 표현의 생성
4. 코드 생성

1. 구문 해석
소스 코드의 해석을 파스(Parse) 또는 구문해석(syntax analyzing)이라고 한다. 또한 소스 코드를 파스하는 프로그램 모듈을 파서(parser) 혹은 구문 해석시(syntax analyzer)라 한다. 그러면'컴퓨터가 이해하기 쉬운 형식'은 어떤 것일까. 그것은 구문 트리(syntax tree)라고 하는 형식이다. 구문 트리는 이름 그대로 트리 구조로 소스 코드의 문법 구조를 그대로 옮겨 놓은 구조이다.
2. 의미 해석
소스 코드를 Parse해서 구문 트리가 만들어졌으면, 다음에는 구문 트리를 해석해서 필요 없는 부분을 없애기도 하고 정보를 추가하기도 하여 추상 구문 트리(AST, abstract syntax tree) 데이터 구조를 만든다. 이 처리를 의미 해석(semantic analysis)이라 한다.
- 어떤 변수가 로컬 변수인지 글로번 변수인지 구별하기
- 변수 선언과 참조를 연결 짓기
- 변수나 식의 타입을 체크하기
- 변수를 참조하기 전에 초기화하고 있는지 조사하기
- 리턴 값이 있는 함수가 값을 제대로 반환하고 있는 체크하기
3. 중간 표현 생성
추상 구문 트리가 생기면, 다음에는 컴파일러 내부에서만 사용되는 중간 표현(IR, intermediate representation)구조로 추상 구문 트리를 변환한다. 일부러 중간 표현을 생성하는 가장 큰 이유는 여러 종류의 프로그래밍 언어나 기계어에 대응하기 위해서다.
예를들어 CPU에 따라 다른 컴파일러가 필요하다. (중간 표현이 없다면) 그러니 상황에 따라 모든 컴파일러를 만들어야 한다면 엄청난 수의 컴파일러가 필요하다. 그러니 만약 모든 프로그래밍 언어가 공통의 중간 표현으로 변환하면 1개의 언어, 1개의 CPU에 대응하는 코드는 각각 1개만 있으면 된다. 그래서 여러 종류의 언어나 CPU에 대응하는 컴파일러는 중간 표현을 사용하는 것이 좋다. 예를 들면 GCC는 RTL(register transfer language)이라는 중간표현을 사용한다.
4. 코드 생성
마지막으로 중간 표현을 어셈블리 언어로 변환한다. 이 단계를 코드 생성(code generation)이라고 한다. 코드를 생성하는 프고그램 모듈이 코드 제너레이터(code generator)이다. 코드 생성 포인트는 프로그래밍 언어와 어셈블리 언어의 차이를 얼마나 줄일 수 있는가 하는 점이다.
5. 최적화
실제 컴파일러에는 코드를 최적화(optimization)하는 단계까 포함된다.

[clojure]nil 체크

nil 과 false를 제외한 모든 것이 반복문이나 조건문에서 true가 된다.

user=> (if nil true false)
false
user=> (if false true false)
false
user=> (if -1 true false)
true
user=> (if 0 true false)
true
user=> (if "false" true false)
true
하지만 비어있는 컬렉션컬렉션은?
user=> (if [] true false)
true
이럴수가. 하지만 이렇게 하면 된다.
user=> (if (not (empty? [])) true false)
false
이게 뭔고생인가. Joy Of Clojure에서는 다른 방법을 제시한다. 바로 (seq s)이다.
user=> (defn print-seq [s]
  #_=>   (when (seq s)
  #_=>     (prn (first s))
  #_=>     (recur (rest s))))
#'user/print-seq
user=> (print-seq [])
nil

여기서는 rest를 쓰고 next를 쓰지 말라고 하는데 왜인지 한번 보자.
user=> (defn my-seq [s]
  #_=>   (when (seq s)
  #_=>     (prn (first s))
  #_=>     (recur (next s))))
#'user/my-seq
user=> (my-seq [])
nil

음... 이게 문제는 아닌 것같다.
user=> (next [])
nil
user=> (rest [])
()
이게 문제인 것 같다.
rest는 시퀀스가 비어있는지 아니던지 nil를 리턴하지는 않는다.
next는 바로 nil을 리턴한다.
우리는 각 반복 시점마다 (seq s)를 쓴다 그러니 시퀀스를 다루는 rest를 넣는게 더 조합이 잘되있다고 보는 것이다. (nil이 나오지 않게 하려 하는 점은 좋은 생각이다.)

** 여담으로 컬렉션을 순회할 때는 재귀보다는 doseq를 사용하는 편이낫다고 한다. 다만 doseq를 사용하면 위철머 살펴 보는 것은 힘들다. 클로저에서 do로 시작하는 구문 (doseq, dotimes, do 등)은 부수 효과를 일으키기 위해 사용하기 때문에 보통 결과로 nil을 리턴한다.

ORA-01722: 수치가 부적합합니다 [해결]

ORA-01722: 수치가 부적합합니다
01722. 00000 -  "invalid number"
*Cause:    The specified number was invalid.
*Action:   Specify a valid number.

이 에러는 딱 봐도 숫자가 문제가 있다는 에러인데, 확인하고 고쳐달라는 부탁(?) 오더(?)를 받았다.
그런데 아무리 봐도 뭐가 문제 인지 알 수가 없었다. 왜냐하면

//가상의 테이블
======================================
SELECT EMAIL, MOBILE, MEMBER_NO FROM INFO Where EMAIL is not null AND USER_CODE=00000000 AND MOD(USER_NO,3)=1;
======================================
이건 잘 나오는데
======================================
SELECT count(*) FROM ( SELECT EMAIL, MOBILE, MEMBER_NO FROM INFO Where EMAIL is not null AND USER_CODE=00000000 AND MOD(USER_NO,3)=1 )
======================================
이건 안되는 것이다.

혹시해서
DESCRIBE를 이용해서 테이블을 확인했다. USER_NO가 VARCHAR임을 확인하고
TO_NUMBER를 한번 붙여보기로 했다.
======================================
SELECT count(*) FROM ( SELECT EMAIL, MOBILE, MEMBER_NO FROM INFO Where EMAIL is not null AND USER_CODE=00000000 AND MOD(TO_NUMBER(USER_NO),3)=1 )
======================================
그래도 안된다
혹시해서 한 번더!
======================================
 SELECT count(*) FROM ( SELECT EMAIL, MOBILE, MEMBER_NO FROM INFO Where EMAIL is not null AND USER_CODE=00000000 AND TO_NUMBER(MOD(TO_NUMBER(USER_NO),3)) =1 )
======================================
된다. 오라클에 대해 잘 몰라서 어떤 일이 일어났는지는 잘 모르겠다. VARCHAR에서 TO_NUMBER 했으면 숫자가 된 것이 아닌가? 그러면 count(*)는 안되고 전부 찍는 건 되는 거지? 좀 더 알아봐야할 것 같다.

clojure 파일 쓰기 (lazy sequence를 만난 순간)

100만개의 가짜 이메일을 만들어야 할 일이 생겼따.
그래서 숫자를 이용해서
0@test.com
1@test.com
2@test.com
3@test.com
4@test.com
.
.
.
1000000@test.com
이렇게 만드는 것인데 클로저로 하니까 아무리 해도 안되서, 결국 자바로 만들었다.
나는 정말 많이 부족한가보다... 라고 생각하고 있던 찰나
일이 끝나고 저녁 먹으면서 한번 REPL을 켜서 테스트해봤다.

(spit "/Users/tom/a.txt" 
  (clojure.string/join "\n" 
    (flatten (for [x (range 0 1000000)] 
                (conj '() (str x "@test.com"))))))
잘된다.
클로저를 하지 말아야 하나...

해당내용을 구글 클로저 그룹스에 물어봤는데 다들 멋지게 풀어 선보이기 시작했다.

(spit "a.txt" (clojure.string/join "\n" 
                (for [x (range 1000000)]

                  (str x "@test.com, " x))))

(spit "a.csv" (with-out-str
                (dotimes [n 1e6]
                  (println (str n "...@test.com, " n))))) 
거기서 나온 조언은 lazy시퀀스가 반환될 때, dall함수나 dorun함수를 이용해서 강제로 실행시켜야 한다는 것이다.
ex)

(def foo (map println [1 2 3]))
#'user/foo
(def foo (doall (map println [1 2 3])))
1
2
3
#'user/foo
lazy sequence에 대한 개념이 부족했었다. 정말... 아직 멀었다.

[clojure][macro] 매크로사용하기 00

Code is Data 라는 말은 코드 자체가 프로그래밍 언어의 자료구조가 된다는 말이다. 클로저 매타프로그래밍(macro)를 하게 되면 우리는 expression level에서 생각하게 된다 rather than at the textual level.

코드 자체가 데이타(자료구조)이기 때문에 이것을 맘대로 바꿀 수 있다.

어쨋든 이게 뭐라는 거야
제대로 알아보자. (REPL을 예로들자)

REPL : Read-Eval-Print loop

Read : 문자단위표시방식 (character-based representation, typically an input stream) 을 받아서 클로저 자료구조로 변환한다. 그래서 Read phase(읽기상태)의 아웃풋은 data(데이타)이다. 이 데이터는 두번째 상태에서 평가된다. (데이터로!)

EVEL : 어떻게 EVAL에서 code-as-data에서 data-as-code로 변할까 (자료구조안에 있는 코드들이 코드로 만들어진 데이터)

read는 표현식(매크로가 잘 작동하는)을 작성 할 때(building the expression) 편한다. 다음의 인풋과 아웃풋을 보자. (어떻게 이게 동작하는지 잘보자.)

첫 매크로는 바로 머리에 때려박아주기 적절한 예시가 될것(ll give you enough to dive headfirst into your first macro.)

실제 클로저의 read 함수는 stream을 사용한다.(consume stream) 이런 행위는 셋업하는데 많은 짓을 해야 할지도 모른다.(verbose to set up.)

이 예제의 목적에 따라. read의 형제격인 read-string을 받겠다. (stream 대신 strings를 받는 것) 
 =================================================
(read-string "(+ 1 2 3 4 5)") 
;==> (+ 1 2 3 4 5)
(class (read-string "(+ 1 2 3 4 5)"))
;=> clojure.lang.PersistentList

 =================================================

여기 이 리스트가 우리가 지금까지 이야기 했던 데이터의 조각이다.
이건 클로저 리스트다. 그리고 아직까지는 클로저 코드다.  (Code as Data)
readstring에서 돌아온 값은 평가될 준비가 되어있다.
이 리스트를 평가할 때, 그 표현식의 값을 알 수 있을 것이라 기대한다.

=================================================
(eval (read-string "(+ 1 2 3 4 5)"))
;=> 15
(class (eval (read-string "(+ 1 2 3 4 5)")))
;=> java.lang.Long
(+ 1 2 3 4 5)
;=> 15
=================================================
이게 REPL에서 실제로 일어나고 있는 일이다. 아니면 완전체 클로저(full-blown clojure project)를 실행할때에도 실제 일어난다. 코드는 자료구조(data structure)로 읽어지고(be read into, 저장되고) 그러고 나서 평가된다(eval). 이 두 스텝(read, evaluate)을 놓고 보았을 때,
read와 eval 사이에 코드자신이 평가되기 위해 자기자신을 끼워넣는 것(꼽사리로 들어가는 것)을 어렵지 않을 것이다.


예를들어 we could replace addition with multiplication:
=================================================


(let [expression (read-string "(+ 1 2 3 4 5)")
  (cons (read-string "*")  ;; 꼽사리로 들어가는 현장
        (rest expression)))
;=> (* 1 2 3 4 5)

(eval *1) ;; *1 holds the result of the previous REPL evaluation
;=> 120
=================================================
이게 무슨 뜻이나면 read-string에다가 일단 곱하기(*)를 넣고 그 다음에는 더하기(+)를 제외한 값(rest)를 가져와서 합친것이다.

물론, 만약 새롱누 리스트를 평가(eval)한다면, 원래 결과와는 전혀 다른 내용이 나올 것이다. 이전에 나온 예시도 나쁘지 않지만 단순히 문자열값을 읽는 것보다는 제대로 표현식을 지어보자.
냅다 이런 "(+ 1 2 3 4 5)" 스트링을 만드는 것은 이상하단 말이야.
하지만 이런 형태의 리스트를 생성하고 싶다면 단순히 (+ 1 2 3 4 5) 이렇게 타입한다고 될 일이 아니다. 왜냐하면 이것 실제로 표현식으로 평가될 것이기 때문이다.
=================================================
(let [expression (+ 1 2 3 4 5)] ;; expression is bound to 15
  (cons
    (read-string "*") ;; *
      (rest expression))) ;; (rest 15)
; IllegalArgumentException Don't know how to create ISeq from: java.lang.Long
; clojure.lang.RT.seqFrom (RT.java:505)
=================================================
보아라 (+ 1 2 3 4 5)가 바로 그냥 15로 평가 되는 것이다. 왜냐하면 (실제로 표현식으로 평가되기 때문에!)

그래서 다른 솔루션이 필요하다. 하나는 실행을 억제하는 거다. 운좋게도 클로저에는 quote라는 동사(verb, 이 verb에 유념하라 function이 아니다)가 똑같은 일을 한다.
=================================================
(let [expression (quote (+ 1 2 3 4 5))]
  (cons (quote *)
        (rest expression)))
;=> (* 1 2 3 4 5)

(eval (let [expression (quote (+ 1 2 3 4 5))]
  (cons (quote *)
        (rest expression))))
;= 120
=================================================

quote verb는 사실 클로저의 특별한 형태(Clojure special form)이며 함수가 아니다.  우리는 이 verbs를 (공식적인 의미는 아님) 함수,매크로 그리고 special forms의 결합체라고 생각하면 된다.

리스트(Lists) 또한 verb 위치로 보여질 수 있는데 평가(eval)될 때는 함수위치로 축소되어야 한다.

함수(function)만이 아규먼트(여기서 아규먼트란 평가될 모~든 데이터를 말하는 듯 하다)를 제대로 평가한다. (컨트롤흐름을 코드(구현한 verbs)로 패스하기전에)
  * Only functions uniformly evaluate their arguments before passing control to the code that implements the verb.


=================================================
(defn print-with-asterisks [printable-argument]
  (print "*****")
  (print printable-argument)
  (println "*****"))

(print-with-asterisks "hi")
; *****hi*****
;= nil
=================================================
verb - 어떻게 정의하나? - 언제 아규먼트가 평가되나?
Functions - defn - Before executing the body
Macro -defmacro - Depends on macro; possibly multiple times or never.
Special form - We can’t! They’re only defined by the language. - Depends on special form; possibly multiple times or never.

자 위에 나온 print-with-asterisks같은 걸 만들 때는 굳이 언제 아규먼트가 평가(timing of the argument evaluation)되는지 알필요가 없다.

하지만 만약! 표현식을 아규먼트로 쓴다면! 그 해당 함수(print-with-asterisks)가 평가되기 전에 평가된다. (???)
 =================================================
(print-with-asterisks
  (do (println "in argument expression")
  "hi"))
=================================================
잘보라 사용하기 전에 이미 김이 다 빠져나간거다.
Macros and speical forms, 는 이 룰에서 자유롭다. 그리고 어떤 순서에서 정의한 거든 아규먼트를 평가할 것이다. (아니면 이상하게 작동해서 망하던가...)  이게 함수와 매크로의 큰 차이란다.

quote expression (quote ...)안에 있는 코드는 평가되지 않는다. 그리고 아주 단순한 토큰들도 가능하다. (심볼, 리스트, 벡터.. 모두)
=================================================
(quote 1)
;= 1
(quote "hello")
;= "hello"
(quote :kthx)
;= :kthx
(quote kthx)
;= kthx
=================================================
근데 귀찮게 항상 QUOTE을써야 하나? 아니다 더 좋은게 있다. 
Reader macro라는 것이다. 이건 우리가 만들 수 있는게 아니다. 언어딴에서 만들어진거라 한다.
=================================================

'(+ 1 2 3 4 5)
;= (+ 1 2 3 4 5)
'map
;= map
user.core=> map
#object[clojure.core$map 0x589a6fa "clojure.core$map@589a6fa"]
user.core= 'map
map
=================================================
오... 확실히 먼가 다르다.
=================================================
(let [expression '(+ 1 2 3 4 5)]
  (cons '* (rest expression)))
;= (* 1 2 3 4 5)
=================================================