2017년 2월 19일 일요일

[clojure] Udemy 클로저 강의를 듣고

이 언어는 나에게 많은 시련을 준 언어이다.
물론 자바를 처음 배울 때 처럼 아침부터 저녁까지 공부한 것은 아니지만 참으로 나의 머리의 한계를 시험하는 언어였다.
클로저를 처음 배우려고 했던 이유는 폴 그레니엄 때문이었다. 내가 일단 이 사람을 어떻게 접하게 되었는지를 적어야 겠다.
나는 문예창작학과로써 서울에 올라온 이후 항상 서점에 갔다.
삼성동에서 학원을 다닐 때에는 코엑스 지하 1층에 있는 영풍문고를 갔다. 매일매일 저녁을 먹지 않고 혹은 저녁 9시에 학원이 끝났을 때, 나는 영풍문고에서 나의 무지 혹은 나의 미래에 대한 불안감, 거기서 오는 허기짐을 문고에서 채웠다.
아무것도 모르면서 계속 읽었다.
그 중에 [해커와화가]라는 책이 있었다.
제1장을 읽고, 다음장을 읽으려 할 때, 문고가 닫는다는 알림과 음악이 흘러나오기 시작했다. 나는 그 책을 사서 집으로 가져가 전부 읽었다.
그의 주장에는 확신이 있었고, 또 그 확신이 허세가 아니라는 것을 이 책 하나만으로 짐작할 수 있었다.
나는 그의 홈페이지를 들어가보았다. 그는 리스프형 언어 Arc를 소개하고 있었다.
리스프형이 무엇인지 궁금하여 lisp에 대해 찾아보기 시작했다.
그리고 scheme를 한 번 사용해보았다.
common lisp도 한 번 만져보았다. (정말... 만져만 보았다)
내가 클로저를 공부한 이유는 다름이 아니라. 내가 보기에 한국 책이나, 외국 원서나 clojure에 대한 책이 더 많아 보였기 때문이다.
꽤나 많은 책을 보았지만 아직도 clojure에 대해 익숙하지 않다. 내가 일로써 사용하지 않아서 그런 것 같기도 하다.
아직 나는 클로저에서 async에 대해서는 전혀 모른다.(구경만했을뿐...)
병렬프로그래밍이라하면 atom, refer 정도 사용할 뿐일 듯 싶다.
한국 책이던, 외국 책이던 정보가 부족한 것은 참으로 안타까운 듯 싶다. 그래서 사람들이 클로저를 사용하는 듯 싶다.
그나마... 자바와 연결이 되어 있어서, nslookup이라던가. 여러 네트워크 프로그래밍에 자바의 메소드를 사용하는 경우가 있는 것같다.
나는 이제 클로저에서 벽에 부딪힌 느낌이다. 뭐 아는 것도 없는데 벽이라니... 그래서 기초라도 제대로 다지자고 Udemy에 있는 강의를 구매했다.

나름 괜찮았던 것 같다. 하지만 더 많은 정보가 있으면 좋을 텐데... 일단 리스프형 언어에 대해서도 아직 이해가 부족한 상태에서 미지의 세계로 나아가려니 참으로 답답하지 짝이없다.


그래도 고민으로 끝나지않고 계속 나아갈 것이다. 이 길이 망하는 길이라 할지라도
배움은 새로운 곳을 나아가기 위한 것이 아니라
같은 곳에서 새로운 것을 바라보기 위한 것이라 믿으니까.
어느날 다시 되돌아와도...
난 달라져 있으리라

2017년 2월 18일 토요일

[java][byte] 0xff 누구냐 넌!

제목에만 적어놓고 언젠가 적어야지 적어야지 했던 글을 이제야 적는다.
자바를 사용할 때 가끔 0xff를 사용해야 할 때가 있다. 없다고? 음... 그렇다면 당신 대신 누군가 해준 것이니 모른다는 것에 감사하시길...

혹시 unsigned int와 singed int를 아는지? 아아 아주 단순한 문제이니 대답을 해주겠다.
unsigned int : 사인(부호)이 안되어있는 int값
signed int : 사인(부호)이 있는 int값
좀 더 자세히 말하면 부호가 있는 int값과 없는 값 둘로 나뉜다. 그렇다면 자바에서 사용하는 int는 무슨 값일까?

???
모를 수도 있고 알 수도 있다. 비전공자인 나로썬 뭔말인가 싶었다. 하지만 다 정보처리기사에 나오니 걱정하지말라. 누가 물으면 이렇게 말해라.
'그거 정보처리기사에서 다 공부했죠.'
자바에는 int건 byte건 unsigned란 없다. signed만을 (이렇게 해서 설계를 잘했다고 하는 강의도 본 적이 있는데 난 허접이라 잘 모르겠다.) 사용한다. 맞다. 부호가 있는 숫자를 사용한단 말이다. 부호가 없는 int는 사용하지 않는다. 뭐야? 그럼 우리가 알 필요가 없잖아??
유감스럽게도 아니다
왜냐하면 세상은 unsigned int로 이루어진 것들이 꽤나 있기 때문이다. 일단 그렇게 사용하던 코드들이 있고 가장 중요한 것은 이 값은 바이트 타입으로 받았을 때이다.
한 번 바이트 타입으로 127 이상으로 값을 넣어보자.
public static void main(String[] args) {
  byte[] a = new byte[] {127, 0, 0, 1};
  byte[] b = new byte[] {128, 0, 0, 1};
}
a는 되고 b는 안된다. 삶과 죽음은 종이한장 차이라더니, 1의 차이가 프로그램을 아예 실행할 수 없게 만들고 있다.
왜그럴까?
바로 자바는 항상 부호가 있는 숫자이기 때문이다.
바이트는 8비트다. 8비트이면 256개를 표현할 수 있는데, 왜 128이 안된다는 것일까?
잘 생각하자 우리는 부호가 있는 값을 이용한다.
256을 반으로 뚝 잘라라


128 | 128 오!! 되잖아!!! 라고 말 할 줄 알았다. 여기서 한 놈은 1을 빼야한다.
왜? 0을 누군가는 표기해야 하니까.
지금은 그래서 0000 0000 이놈이 0을 표현하게 되는데 이 0이 누구꺼냐는 거다.
결론을 말하면 자바 바이트 타입은 -128 ~ 127 이렇게 표현할 수 있다. 그러니 128은 표현할 수 없는 존재가 되는 것이다.
public static void main(String[] args) {
  byte[] b = new byte[] {(byte)128 , 0, 0, 1};
  System.out.println( (byte) 128 );
}
이거 마이너스가 뜬다. 왜냐하면, 128은 int다. 그걸 바이트로 내가 지금 강제로 형변환을 했다. 128은 2의 7승이다. 그 말은 1000 0000 이다.

일단 맨 앞에 1이 있으면 부호가 음수이다.

public static void main(String[] args) {
  byte[] b = new byte[] {(byte)128 , 0, 0, 1};
  System.out.println( (byte) 128 );
  System.out.println((byte) 128 & 0xff);
}
바로 우리가 알아봐야 할 0xff 누구냐넌! 이놈은 이렇게 생겼다. 쟤는 16진수라는 거다.
잠깐 나가지말고 들어보라. 정말 별거 아니니까!!!
0x는 16진수 앞에 붙는 기호라고 하고.
f는 15을 뜻한다.
f가 두개다. 이것 이런 말이다. 15*16^1 + 15*16^0 이거 한번 계산기를 돌려보자.
System.out.println(15*16 + 15*1);
이건 255가 나온다. 255? 어디서 많이 들어 본 것 같지 않은지? 맞다. 우리 네트워크 설정할 때 많이 사용한다. 바로 그 때!!!! 사용되고 있을 지도 모르는 놈이 0xff이다.

여튼 이놈을 이용해서 &(and)연산을 하면 부호가 없는 없는 그냥 숫자가 나올 것이다. (왜?)
잘 생각해보자. 255는 일단 부호가 있는 숫자에서 255라는 숫자가 나온것이다.
그러면 잘 생각해보자.
기호가 없는 255는 1111 1111 이다. 기호가 있는 255는? 무조건 앞이 0이다. 게다가 255는 int라니까? 부호가 있는 32비트다. 32비트!!!
0000 0000 0000 0000 0000 0000 1111 1111
0xff의 정체이다. 자 그런데 어떻게 저 (byte) 128 & 0xff 가 어떻게 되는거지?
자!! 이제 얼마 안남았다.
1111 1111 1111 1111 1111 1111 1111 1111 1000 0000
0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 (and) 연산 실시
----------------------------------
0000 0000 0000 0000 0000 0000 0000 0000 1000 0000
이러면 이제, 부호가 마이너스가 아니라! 부호가 플러스인 128라는 숫자가 된다.
휴... 금방 끝날 줄 알았는데... 하... 힘들었다.


이 글이 정말 서툴지만 누군가에게는 많은 정보가 되길 바랍니다. 그리고 잘못 적은 내용을 지적해주신 분께 감사의 뜻을 전합니다. ^^

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)
=================================================

2016년 12월 22일 목요일

클로저로 단순한 GUI 프로그램을 만들어보았다.

아주아주 단순한 클로저 프로그래밍을 해보았다. 이전에 웹으로 연결도 해보고 클로저스크립트도 한번 경험해보았지만 아직도 클로저 라는 언어에 확신이 없다. (왜냐하면 내가 제대로 모르기 때문...ㅠㅜ)
그래서 하나를 짬내서 만들어 보았다. 처음에는 swing을 썼다. 참으로 고통이었다. seesaw라는 라이브러리가 있는 걸 확인하고 seesaw를 살짝 사용했다.
저녁에 짬내서 만들면서 느낀 점은 참으로 힘들었다는 점이다.
나의 능력이 문제인지, 내 뇌가 아직 클로저라는 리스프계열에 적응을 못하고 있는 것인지 잘 모르겠다.
막연히 Swing을 쓰면 되겠지라고 생각했었는데... 정말 자바로 그냥 짰으면 순식간에 만들었을 것이라는 생각이 들었다.
아직 내가 클로저를 짬내서 3개월 정도 해봤는데 어렵다는 것은 내가 아직 클로저가 가지는 개념을 모르고 있다는 생각이 든다.

하지만 확실한 것은 리스프 프로그래밍은 정말 어렵다. 처음으로 하는 GUI를 다루는 것이라 버튼 하나 넣는 것에도 애를 먹었다. 내가 고작 만든 것은 이메일만 필터링해서 나열하는 것이다.
대량메일을 보내기 위해서!
그래서 만약
AAA@AAA
BBB@BBB.com
CCC@CCC.net
뭐 이런게 있으면
BBB@BBB.com, CCC@CCC.net
뭐 이렇게 만들어 주는 것이다. 정말 별거 아닌 것을 만들었다. REPL에서 그냥 치면 바로 나오는 걸 만든 것이다.


그런데 참으로 느낀 점은...
재미있었다.
내 앞에 있는 무언가 보이지 않는 큰 힘이 꿈틀거리는데 잡을 수 없는... 그런 느낌?
잡으려고 마구 휘저었지만
잡지 못했고 별거아닌 노력의 산물만 남았지만
정말 재미있었다.
뭔가 쌓아올리는 느낌?

어쨋든 github로 관리하면서 더 손봐야겠다. (과연 할런지...)
https://github.com/ssisksl77/sman

2016년 12월 21일 수요일

[jvm]04 - Instruction Set Summary

Instruction Set Summary

JVM명령은 1바이트 연산코드(opcode)로 구성되어있다.뒤에 딸려오는 제로(zero) 또는 다른 피연산자(operands)는 연산에 쓰인다.많은 명령어들은 피연산자가 없고 그냥 연산코드만 가지고 있다.
예외는 무시하고, JVM인터프리터의 내부 loop는
이렇게 생겼다고 한다. 피연산자의 수와 사이즈는 연산코드에 의해 결정된다. 만약 피연산자의 사이즈가 1바이트보다 크다면?

그것들은 빅엔디안(big-endian order : 데이터의 최상위 비트가 가장 높은 주소에 저장되므로 그냥 보기에는 역으로 보인다.)방식으로 저장된다.

예를들어, 로컬변수 unsigned 16비트 인덱스는 두개의 unsigned 바이트로 저장된다.(byte1 과 byte2).
값은

 이다.


Types and the Java Virtual Machine

JVM 명령어 셋의 대부분은 명령의 타입정보를 인코드한다.
예를들어 iload명령은 로컬 변수(int값이어야만 한다)의 내용을 피연산자 스택에 로딩한다.
floadiload와 똑같이 행동한다.(물론 float값을 사용한다.)
두 명령은 똑같은 구현을 가지고 있지만 서로 다른 연산부호(opcode)다.
  • i : int operation
  • l for long
  • c for char
  • f for float
  • d for double
  • a for reference
어떤 것들은 타입문자(type letter)가 없다. 예를들어 arraylength는 항상 object를 실행하지만 배열이다. goto를 보자. 무조건적인 제어 전환(control transfer), 타입이 있는 피연산자를 실행하지 않는다.

[jvm]03 Special Method

1. Special Method

자바로 만들어진 모든 생성자는 instance initialization method로 나타내어진다. (그놈의 이름은 ) 이 이름은 컴파일러에서 볼 수 있다. (왜 컴파일러에서?) 왜냐하면 은 유효한 식별자(identifier)가 아니기 때문이다. 이것은 자바 언어로 바로 씌여질 수 없다. 인스턴스 초기화 메소드(Instance initialization methods)는 오로지 JVM안에서 invokespecial명령어로 호출된다. 그리고 이것들은 오로지 초기화안된 클래스 인스턴스에서 호출될 것이다. 인스턴스 초기화 메소드(instance initialization method)는 생성자의 엑세스 권한을 가진다.

클래스 또는 인터페이스는 최소한 한나의 초기화 메소드가 있다. 그리고 그 메소드를 호출해서 초기화가 진행된다. 그 초기화 메소드는 아규먼트가 없으며 void이다. 이라는 특별한 이름을 가진다. class 파일의 version number 가 51이상이면, 추가로 ACC_STATIC 세팅값이 있어야 한다. 은 컴파일러에서 제공한다. 이유는 은 유효한 식별자(identifier)가 아니기 때문(init과 마찬가지) Class and interface initialization methods는 JVM에 의해 내부적으로 호출되어진다. 이것들(methods)은 어떤 JVM명령어로도 직접적으로 호출될 수 없다. 하지만 간접적으로는 호출될 수 있다.(class initialization process에서)

2. Exception

JVM에서 예외(exception) 단순히 Throwable, 아님 Throwable의 subclass의 클래스 인스턴스이다. 예외를 던지는 것은 예외가 던져진 곳(point)에서 즉각적 로컬 외(nonlocal) 컨트롤의 이전이 된다. 대부분의 예외는 동기적으로 일어난다. 비동기적인 예외 또한 프로그램 실쟁 중에 일어날 수도 있다.