레이블이 clojure인 게시물을 표시합니다. 모든 게시물 표시
레이블이 clojure인 게시물을 표시합니다. 모든 게시물 표시

2022년 1월 2일 일요일

이맥스로 블로그 배포하기

이맥스 org mode로 작성한 글을 블로그로 배포해보았다.
css를 좀 더 만져볼까 했지만, 역시 치장보단 글 자체가 중요하다고 생각해서 글이나 써야겠다고 생각한다.
새로 만든 사이트에는 좀 제대로 써보려고한다. 기존에 회사에서 썼던 기술블로그들도 좀 다시 정리해서 올려볼까 한다.
나의 사견이 들어간 글이 필요하다. 왜냐하면, 정보는 어짜피 책을 보는 것이 더 정확하기 때문이다.
나는 개발자로서 기술을 말로 유창하게 말하는 것을 좋아하지 않는다. 이것이 옳고 저것이 그르다. 라는 것을 말로만 유창한 사람들을 피한다.
개발자라면 기어코 코드로 먼저 말하는 사람들이 좋다.
개발은 이상하게 하면서 말은 유창하고 잘하는 것처럼 말하는 사람들이 많다. 하지만 그것을 말만 듣는 사람들은 잘 모른다.
복싱분석은 선수보다 비평가들이 더 잘하는 법이니까. 이론만으로는 그가 복싱을 잘하는지 알기 어려울 것이다.
그래서 새로운 기술블로그에는 코드를 보여주고 짧게 설명하는 식으로 가볼까한다.
그 첫번째 clojure에서 유명한 라이브러리 중에 하나인 ring 프로젝트의 소스를 까보고 직접 간단하게 만들어서 돌려보는 것을 주제로 삼았다.
https://ssisksl77.gitlab.io/code-that-talks/220122-ring.html

2021년 11월 27일 토요일

매크로에 대한 글을 적다

https://green-labs.github.io/the-macro

이직한지 6개월이 되는 시점에 드디어 기술블로그를 올리게 되었다. 입문자를 위해 자세한 설명을 적거나 하진 않았지만 최소한 말로 약만 파는 것이 아니라, 나의 코드로 가능하다는 것을 보여주고 싶었다. 개발자는 코드로 말하는 것이 기본이니까.

LISP 매크로는 개발자에게 만지기 어려운 무서운 기술처럼 보이는가보다. 실제로 쉽지는 않다. 
주류의 인사들은 '매크로를 쓰는 가장 좋은 방법은 쓰지 않는 것'이라고 한다. 그 만큼 매크로는 과한 기술이라는 평이 지배적이다.

하지만 과한 기술이라고 쓰지말라고 하는 것은 웃기다. 기술에 대한 이해도가 부족할 때 과하게 사용하니까 말이다. 즉, 이 말은 나에게 '넌 잘 모르니까 쓰지마' 라는 말로도 들린다.

매크로를 쓰지 말아야 하는 가장 큰 이유는 제대로 사용할 수 없는 개인의 이해도만이 이유가 되어야 한다고 주장하겠다.

어떤 기술이던 이해하지 못하고 쓰면 화를 면치 못한다. 반복문/조건문을 이해하지 못하고 쓴다면 안전할까? 죽음이 무서운 이유는 우리가 죽음을 모르기 때문이라는 말이 있듯이 매크로가 어둠의 기술처럼 느껴지는 이유는 그 만큼 매크로를 모르기 때문이라고 생각한다. 매크로는 죽음과 다르게 다행스럽게도 관심이 있으면 이해할 수 있다. 즉, 매크로가 두려운 이유는 매크로를 온전히 받아들이려고 하지 않는 나의 마음일 것이다.

난 권위에서 오는 말을 좋아하면서도 싫어한다. 여러 잠언들은 때때로 나를 편안하게 하지만 어떤 문장들은 맞는 말 같으면서도 고개를 갸우뚱하게 만드는 경우가 있다. 이때 '권위자'라는 단어는 그 갸우뚱하던 고개를 끄덕이게 만든다. 알아서 고개를 숙이거나 주위에서 숙이도록 강요하게 하는 힘이 있다.

'매크로를 쓰지말라' 라고 말한다면, 우리는 매크로가 무엇인지 정확히 알 필요가 있다. C, LISP의 매크로는 모두 컴파일러가 미리 만들어놓은 룰을 초월한다. 

이 초월하는 힘을 처음 접한 나는 다음과 같은 경험을 하였다.  
첫째, 내가 한번 휘두른 이 힘이 어디까지 닿는지 가늠이 되지 않는다.
둘째, 기존의 힘이 무색함을 느낄 수 있다.
셋째, 결국 강력한 이 힘에 집착하게 된다.

아마 구루들은 세번째에 이르는 것에 두려움을 느끼게 하고 싶었다고 생각한다.
그리고 구루들의 "매크로를 잘 쓰는 방법은 쓰지 않는 것이다" 라고 말하는 것에 대한 숨겨진 또다른 의도는 아래와 같다고 생각한다.

"매크로는 어짜피 기어코 위험을 무릎쓰고 자유로운 힘을 얻고자 하는 사람들이 쓰는 기술이다. 쓰지말라고 쓰지 않는 사람들에게는 필요없다. 어짜피 매크로를 쓰고자 하는 사람들은 나의 말이 의미 없다. 이미 쓰고 있을테니"

나는 위처럼 이해를 했다. 나는 그래서 매크로의 강력함과 위험성을 인지하고 자유롭게 쓰려고 한다. 만약에 그분들이 자신들은 그런 의미로 말한 것이 아니라고 말하면 어떤가?

그런 것들은 이미 상관없다.

2021년 3월 1일 월요일

Learn Reagent Pro by Jacek Schae 강의 후기

reagent 란

reagent react를 clojureScript로 개발할 수 있게 해주는 라이브러리다. 

html을 만들 때는 hiccup라이브러리 처럼 clojure 자료 구조로 표현이 가능하다. 

즉 일반적으로 컬렉션을 다루는 함수를 그대로 적용해도 잘 작동한다. 

더 특이한 것은 reagent의 atom이다. 

clojure에서 atom은 java의 AtomicInteger, AtomicReference와 같은 녀석처럼 병렬 프로그래밍을 할 때 사용한다. 

reagent 에서 atom은 react, redux의 조합처럼 값을 변경시키면 저절로 해당 atom을 dereference해서 사용하는 구간이 다시 렌더링 되면서 값이 보여진다.

Learn Reagent Pro

강의는 꽤나 비싼데 reagent를 처음 접하는 사람에게는 유용할 것이다. 

reagent만 배우는 것이 아니라, shadow-cljs 같이 의존성을 적용하는 방법이나, 일반적으로 reagent를 사용할 때 어떻게 사용하는지 알 수 있다.

현재 5강 정도를 남겨두고 firebase에 올려보기 까지 진행했다. 

꽤나 유익했고, 다음 강의 Learn Reframe Pro도 이어서 수강할 예정이다. 
  

2021년 2월 27일 토요일

clojure로 카프카에 이벤트 발행

의존성


[org.apache.kafka/kafka-clients "2.5.0"]

사용코드


(ns kafka-demo2.core
  (:import [org.apache.kafka.common.serialization StringSerializer]
           [org.apache.kafka.clients.producer KafkaProducer ProducerRecord])
  (:gen-class))

(def config
  {"bootstrap.servers" "127.0.0.1:9092"
   "key.serializer" StringSerializer
   "value.serializer" StringSerializer})

(defn publish [topic str]
  (let [producer (KafkaProducer. config)
        record (ProducerRecord. topic str)]
    (try
      (.send producer record)
      (finally (.close producer)))))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (println "push A")
  (publish "yhnam-topic" "A"))

도커

https://github.com/ssisksl77/clojure-breadcrumb 위 깃허브 참고

실행

별거 없음 위 깃허브 참고

$ docker-compose up
$ lein uberjar
$ java -jar kafka-demo2-0.1.0-standalone.jar

그외 정리

https://www.notion.so/tombox/db6bc836f25d41948d28f3aff9ec1aff

2020년 1월 30일 목요일

[리뷰] clojure polymorphism

이 책은 leanpub이라는 사이트에서 구매한 책이다.

저자는 코드의 재사용에서 가장 중요한 요소를 다형성이라 믿는 듯 하다.
하여 다형성에 대한 자신의 생각과 그 노하우를 clojure를 이용해서 아주 짧게 설명해주고 있다.


노트정리한 내용

2019년 9월 11일 수요일

[clojure programming] 2. Functional Programming - 로깅파일 만들기

출처 : Clojure programming
Building a Primitive Logging System. with Composable Higher-Order Functions

(defn print-logger [writer]
  #(binding [*out* writer]
     (println %)))

(def *out*-logger (print-logger *out*))
(*out*-logger "hello")

버퍼를 만들어 보자.
(def writer (java.io.StringWriter.))
(def retained-logger (print-logger writer))
(retained-logger "hello-1")
(retained-logger "hello-2")
(str writer)

파일에 써보자.
(require 'clojure.java.io)
(defn file-logger [file]
  #(with-open [f (clojure.java.io/writer file :append true)]
      ((print-logger f) %)))

(def log-file (file-logger "message.log"))
(log->file "HELLO FILE")
지금까지는 아주 적은 코드로 서로 다른 곳에 쓸 수 있는 방법을 알아봤다.
만약, 여러곳에 써야 한다면 어떻게 해야 할까. doseq를 쓴다.
(defn multi-logger
  [& logger-fns]
    #(doseq [f logger-fns]
       (f %)))
(def log (multi-logger
           (print-logger *out*)
           (file-logger "message.log")))
(log "hello console and file")

마지막 예제, 진짜 로깅인 것처럼 시간 포맷까지 앞에 붙여보자.
Adding a piece of "logging middleware" to include a timestamp with each log message
(defn timestamped-logger
  [logger]
  #(logger (format "[%1$tY-%1$tm-%1$ts %1$tH:%1$tM%1$tS] %2%s" (java.util.Date.) %)))

(def log-timestamped (timestamped-logger
                      (multi-logger
                        (print-logger *out*)
                        (file-logger "message.log"))))
(log-timestamped "goobye, now")

2019년 8월 23일 금요일

[on lisp] 5.2 Orthogonality (직교성)

An orthogonal language is one in which you can express a lot by combining a small number of operators in a lot of different ways.
직교성이 있는 언어는 작은 여러 연산자들을 이용하여 많은 것들을 표현할 수 있다.(그리고 그 방법 또한 여러가지임)

Toy blocks are very orthogonal; a plastic model kit is hardly orthogonal at all.
레고블록은 직교성이 있지만, 특정 모델 키트는 직교적이지 않다.
The main advantage of complement is that it makes a language more orthogonal.
complement함수의 주된 장점은 언어를 더욱 직교적으로 만든다. (complement는 반대를 말함. (보수))

complement, 커먼리습은 함수의 쌍을 가진다. remove-if/remove-if-not, subst-if/subst-if-not 같은 것.
complement와 함께먼 이것들의 반은 필요 없게 된다.

The setf macro also improves Lisp's orthogonality.
setf 매크로 또한 Lisp의 직교성을 향상시켜준다.
Earlier dialects of Lisp would often have pairs of functions for reading and writing data.
리스프의 초기 방언들은 종종 데이터를 읽고 쓰는 기능을 가지고 있었다.
With property lists, for example, there would be one function to establish properties and another function to ask about them.
예를들어, 프로퍼티 리스트를 함수로 간주하여 거기에다가 값을 넣고 뺄 수 있다고 해보자.
프로퍼티 리스트에는 값을 넣고 빼는 기능이 각각 하나씩 있을 것이다.
In Common Lisp, we have only the latter, get.
커먼리스프에는 후자만 있다.(빼는 기능) get

To establish a property, we use 'get' in combination with setf:
프로퍼티를 설정하려면 setf와 함께 'get'을 사용한다.
(setf (get 'ball 'color) 'red)
(print (get 'ball 'color))
'red
이렇게 넣을 수 잇다.


We may not be able to make Common Lisp smaller, but we can do something almost as good: use a smaller subset of it.
우리는 커먼리습을 작게 만들 수는 없을 것이다, 하지만 거의 그렇게 할 수 있다:작은 집합을 사용하는 것.
Can we define any new operators which would like complement and setf, help us toward this goal?
setf나 complement같은 연산자를 더 만들면 우리의 목표에 더 가까워질까?

There is at least one other way in which functions are grouped in pairs.
기능이 쌍으로 그룹화되는 다른 방법이 최소한 하나 이상있다.
Many functions also come in a destructive version: remove-if/delete-if, reverse/nreverse
한 가지 기능에서 destructive version(리턴하지 않고 값을 바꿔버리는)을 직교 기준으로 보고 여러개의 함수를 제공할 수도 있다. remove-if/delete-if, reverse/nreverse 그리고 append/nconc
처럼 이번에는 그룹의 방식의 기능의 정반대강 아니라 destructive냐 아니냐로 나눈것.
By defining an operator to return the destructive counterpart of a function, we would not have to refer to the destructive functions directly.
이제 특정 함수에 대해서 destructive counterpart를 리턴하는 함수를 함수를 정의하여, 직접적으로 destructive함수를 부를일 없게 만들자.(기준을 만들고 반으로 잘라서 없애버리는 거다)

어떻게? Fig5.1을 보면서 이야기 하자.
;; FIg5.1 : Returning desturctive equivalents
(defvar *!equivs* (make-hash-table))

(defun ! (fn)
  (or (gethash fn *!equivs*) fn))

(defn def! (fn fn!)
  (setf (gethash fn *!equivs*) fn!))
그림5.1은 descturvie counterparts 개념을 지지하는 코드를 가지고 있다.
글로벌 해시테이블 *!equivs* 맵은 destructive equivalents를 위한 기능을 한다.
!함수는 destructive equivalent를 리턴한다.
def!는 *!equivs*에 값을 저장한다.

The name of the ! (bang) operator comes from the Scheme convention of appending ! to the names of functions with side-effects
이름에 !를 붙이는 연산자는 스킴에서 따온 건데 사이드이팩트가 있으면 붙이는 것이다.

자 어떻게 쓰는지 보자.
(def! #'remove-if #'delete-if)
delete-if를 없애버린 것이다. 이제 remove-if만으로 개발이 가능하게 되었다.
아래를 보자.
(delete-if #'oddp lst) ;; 기존에는 이렇게 썼다. 이제 remove-if만으로 개발이 가능하다.

(funcall (! #'remove-if) #'oddp lst)

(defvar *!equivs* (make-hash-table))

(defun !- (fn)
  (or (gethash fn *!equivs*) fn))

(defun def!- (fn fn!)
  (setf (gethash fn *!equivs*) fn!))

;; test
(setf lst '(1 2 3 4 5))
(def!- #'remove-if #'delete-if)


(print (remove-if #'oddp lst))
(print lst)

(print (funcall (!- #'remove-if) #'oddp lst))
;; or ((!- #'remove-if) #'oddp lst)
(print lst)
자 이렇게 새로운 직교규칙을 만들어서 쪼개버린다. 그렇게하여 작은언어 아니 작은 집합으로 쪼개진 언어를 가지게 된다.
(더 작은 블록은 가지게 된 것같은 느낌인데? 이렇게 desctructive에 대한 함수가 각각 두개씩 있는 것에는 블록에 유형이 있을 것이다.
우리는 그것이 뭔지는 모르지만 이렇게 임의로 쪼개버릴 수 있다.)
추가로 나는 online compiler에서는 !(느낌표)를 정의할 수 없어서 !-로 했다.

As well as greater orthogonality, the ! operator brings a couple of other benefits.
직교성을 높인 것 뿐만 아니라. !연산자는 다른 이점을 준다.
It makes progames clearer, because we can see immediately that (! #'foo) is destructive equivalent of foo.
프로그램을 깔끔하게 한다. (! #'foo)를 보면 바로 foo의 destructive버전인 것을 알 수 있다. (remove-if/delete-if는 헷갈릴 수밖에 없다)
Also, it gives destructive operations a distinct, recognizable form in sourece code, which is good because they should receive special attention when we are searching for a bug.
또한 destructive 연산자에 뚜렷하고, 눈에띄는 소스코드 형태를 제공한다. 이렇게 함으로써 버그를 찾을 때, 특별한 주의를 줘야 하는 부분(이렇게 값이 변하는 곳)을 쉽게 찾을 수 있게 된다.

Since the relation between a function and its destructive counterpart will usually be known before runtime,
destructive에 직교성에 대한 함수 관계까 런타임 이전에 알 수 있기 때문에
it would be most efficient to define ! as a macro, or even provide a read macro for it.
!를 매크로로 만드는 것이 유용할 것이다, 또한 더 나아가 이것을 위한 read macro를 제공한다. (여기서 read macro는 뭔지 모르겠음 아직 안나온 이야기)

직교성이 이렇게 중요한 것이었구나...
라는 생각을 한다.

2019년 2월 15일 금요일

[clojure] 4clojure easy버전 0에서 50까지 문제 풀고 중간점검

clojure로 뭘 할지 감이 잡히지 않아서, 그냥 4clojure나 가끔씩 풀어보기로 했다.
elementary, easy부분만 풀었는데 괜찮았다. 글자그대로 쉬웠지만 어떤 것들은 꽤나 생각을 해야 했다.
4clojure를 풀다보면 내 뇌의 생각하는 방식이 바뀌어야 한다는 느낌이 든다. 운동을 할 때도 가동범위가 중요한데, 사용하지 않던 뇌부분을 사용한 느낌이 들면서, 나의 뇌 가동범위가 부족한 것이 아닌가 싶었다.

clojure로 언젠가 개발을 하지 않더라도, 이 문제를 풀면서 뭔가 개발을 할 때 생각하는 방식이 늘어나는 느낌을 받을 것 같다. (좀더 난이도가 올라가면 말이다)

결과적으로 4clojure를 풀기로 결심한 것은 만족이다.
https://github.com/ssisksl77/clj-web-demo/blob/master/src/web_demo/4clojure/elementary.clj

51~100도 금방 가자.

2018년 3월 5일 월요일

[clojure][designpattern] 반복자패턴 (Iterator pattern)

일단 자바
Iterator i;
while (i.hasNext()) {
  i.next();
}

Node next = root;
while( next != null )
  next = next.next;

클로저에서 반복자에 해당하는 것
(seq [1 2 3]) => (1 2 3)
(seq (list 4 5 6)) => (4 5 6)
(seq #{ 7 8 9}) => (7 8 9)
(seq (int-array 3)) => (0 0 0)
(seq "abc") => (\a \b \c)

시퀀스인가? 반복자는 단순 시퀀스이다.
(first (seq [1 2 3]))
; 1
(rest (seq [1 2 3]))
; (2 3)
시퀀스가 아닌 것으로도 이렇게 돌아다닐 수 있나? 클로저는 자바다. 인터페이스를 구현하면 된다.

(deftype RedGreenBlackTree [& elems]
clojure.lang.Seqable
(seq [self]
;; traverse element in needed order
))

내 생각에는 그냥 죄다 시퀀스로 쓰면 될 것 같다.

[hackerrank][java8][clojure] SolveMeFirst

int 두개 더하기
java
static int solveMeFirst(int a, int b) {
  return a + b;     
}
clojure
(defn solveMeFirst [x y]    
    (+ x y))
여기서 특이한 점은 stdin, stdout 방법을적어놔야할 것 같다. 자바
Scanner in = new Scanner(System.in);
int a = in.nextInt();
int b = in.nextInt();
int sum = solveMeFirst(a, b);
System.out.println(sum);
클로저
(def a (read-line))
(def b (read-line))

(println (solveMeFirst (Integer/parseInt a) (Integer/parseInt b)))

[clojure][designpattern]책임 연쇄(Chain Of Responsibility) 패턴

비속어를 별표로 변하는 필터를 만들자.
public abstract class Filter {
  protected Filter nextFilter;

  abstract void process(String message);

  public void setNextFilter(Filter nextFilter) {
    this.nextFilter = nextFilter;
  }
}
실제 적용할 필터
class LogFilter extends Filter {
  @Override
  void process(String message) {
    Logger.info(message);
    if (nextFilter != null) nextFilter.process(message);
  }
}

class Profanityfilter extends Filter {
  @Override
  void process(String message) {
    String newMessage = message.replaceAll("fuck","f*ck");
    if (nextFilter != null) nextFilter.process(newMessage);
  }
}

class RejectFilter extends Filter {
  @Override
  void process(String message) {
    System.out.println("RejectFilter");
    if (message.startsWith("[A Project NY]")) {
      if (nextFilter. != null) nextFilter.process(message);
    }
  }
}

class StatisticsFilter extends Filter {
  @Override
  void process(String message) {
    Statistics.addUsedChars(message.length());
    if (nextFilter != null) nextFilter.process(message);
  }
}
1. 각 필터를 만든다. 2. 서로 기차놀이처럼 연결한다. (순서를 만드는 것이다)
Filter rejectFilter = new RejectFilter();
Filter logFilter = new LogFilter();
Filter profanityFilter = new ProfanityFilter();
Filter statsFilter = new StatisticsFilter();

rejectFilter.setNextFilter(logFilter);
logFilter.setNextFilter(profanityFilter);
profanityFilter.setNextFilter(statsFilter);

String message = "[A PROFIT NY] What the fuck?";
rejectFilter.process(message);
clojure 클로저는 각 필터를 함수로 만든다.
;; define filters

(defn log-filter [message]
  (logger/log message)
  message)

(defn stats-filter [message]
  (stats/add-used-chars (count message))
  message)

(defn profanity-filter [message]
  (clojure.string/replace message "fuck" "f*ck"))

(defn reject-filter [message]
  (if (.startsWith message "[A Profit NY]")
    message))
(defn chain [message]
  (some-> message
          reject-filter
          log-filter
          stats-filter
          profanity-filter))
저 some->가 뭐지? some->는 ->와 기능이 같다.
한가지만 빼고 표현식이 nil이면 멈춘다. 그리고 nil을 내뱉는다. nil이 아니면 값을 다음에 넘긴다.
(chain "fuck") => nil
(chain "[A Profit NY] fuck") => "f*ck"
[A profit NY] 가 없으면 reject-filter에서 이미 걸려서 nil이 나옴.

2018년 2월 6일 화요일

[clojure][designpattern] 프로토타입 패턴

프로토타입은 원형틀이라고도 볼 수 있다.
기본적으로 만들어진 틀을 사용하는 것이다.
내용에서는 회원등록양식을 사용했는데 그 틀에 대한 기본틀을 프로토타입이라 하자. 여기서는 몸무게를 적지 않으면 60킬로를 기본값으로 넣는다고 한다.
그리고 중요한 것은 clone()메소드인데 왜 중요하냐면 기존에 만들어진 틀을 아주 쉽게 복사할 수 있는 것이다.

public class RegistrationForm implements Cloneable {
  private String name = "Zed";
  private String email = "zzzed@gmail.com";
  private Date dateOfBirth = new Date(1990, 1, 28);
  private int weight = 60;
  private Gender gender = Gender.MALE;
  private Status status = Status.SINGLE;
  private List children = Arrays.asList(new Child(Gender.FEMALE));
  private double monthSalary = 1000;
  private List favouriteBrands = Arrays.asList("Adidas", "GAP");

  @Override
  protected RegistrationForm clone() throws CloneNotSupportedException {
    RegistrationFrom prototyped = new RegistrationForm();
    prototyped.name = name;
    prototyped.email = email;
    prototyped.dateOfBirth = (Date)dateOfBirth.clone();
    prototyped.weight = weight;
    prototyped.gender = gender
    prototyped.status = status;

    List childrenCopy = new ArrayList();
    for (Child c : children) {
      childrenCopy.add(c.clone());
    }
    prototyped.children = childrenCopy;
    prototyped.monthsCopy = monthSalary;

    List brandsCopy = new ArrayList();
    for (String s : favoriteBrands) {
      brandsCopy.add(s);
    }
    prototyped.favouriteBrands = brandsCopy;
    
    return prototyped;
  }
}
사용자를 만들 때마다, clone()을 호출해서 기본값으로 쓴다.
왜 clone()을 사용해야 하나? 바로 자바가 나를 죽어라 헷갈리게 한 내용인 카피 문제이다.
이것 때문에 나는 getter를 만들때도 그것이 객체라면 복사를 한다. (혹시 몰라서...)


Clojure

(def registration-prototype
  {:name "Zed"
   :email "zzzed@gmail.com"
   :date-of-birth "1970-01-01"
   :weight 60
   :gender :male
   :status :single
   :children [{:gender :female}]
   :month-solary 1000
   :brands ["Adidas" "GAP"]})

;;return new object
(assoc registration-prototype
  :name "WHO"
  :email "EMAIL@DOTCOM"
  :weight 52
  :gender :female
  :month-salary 0)
clojure는 기본적으로 불변객체이기 때문에 clone은 필요없다.
그렇다면 항상 다 모든것을 final로 만들어 복사하면 느려지지 않을까?
느리긴 한데... 아주 느리지는 않다는 것이 클로저들의 주장인 것같다.

2018년 1월 26일 금요일

[clojure] 클로저로 mx-record 구하기 (mx-record in clojure)

예전에 MX-RECORD 받아와야 할 때, 자바로 된 것을 클로저로 바꾸면 어떨까 해서 한 번 짜봤던 것이 있다.

시작은 이렇게 하자. nslookup 을 사용할 것이다.
(use '[clojure.java.shell :only [sh]])

(sh "notepad" "a.txt")
(sh "notepad" "/a.txt")
(sh "pwd")
(sh "nslookup")
(sh "nslookup" "-query=mx" "google.com")

뭔가 보이는 것 같다.
(defn find-mx [hostname]
  (letfn [(nslookup [hostname] (sh "nslookup" "-query=mx" hostname))]
    (nslookup hostname)))

(find-mx "google.com")
자세히 보아하니 뭔가 엄청 나오는 거 같다.
좀 더 정리해보자. 보아하니 내가 찾으려는 내용에는 mail이라는 거와 exchanger라는게 붙어 있는 것 같다.
일단 해보자.
(defn find-mx [hostname]
  (letfn [(nslookup [hostname] (sh "nslookup" "-query=mx" hostname))]
    (remove nil? (map #(re-matches #"(.+)mail\sexchanger(.+)" %) 
                       (clojure.string/split (second (second (nslookup hostname))) #"\r\n")))))

(find-mx "google.com")

(["google.com\tMX preference = 10, mail exchanger = aspmx.l.google.com" "google.com\tMX
preference =
10, " " = aspmx.l.google.com"] ["google.com\tMX preference = 30, mail exchanger = alt2.aspmx.l.goog
le.com" "google.com\tMX preference = 30, " " = alt2.aspmx.l.google.com"] ["google.com\tMX
preference
= 20, mail exchanger = alt1.aspmx.l.google.com" "google.com\tMX preference = 20, " " =
alt1.aspmx.l
.google.com"] ["google.com\tMX preference = 50, mail exchanger = alt4.aspmx.l.google.com"
"google.co
m\tMX preference = 50, " " = alt4.aspmx.l.google.com"] ["google.com\tMX preference = 40, mail exchan
ger = alt3.aspmx.l.google.com" "google.com\tMX preference = 40, " " =
alt3.aspmx.l.google.com"])

나온 거 같다. 다른 테스트도 해보자.
user=> (count (find-mx "google.com"))
5
user=> (count (find-mx "google33.com"))
0
user=> (count (find-mx "naver.com"))
3

MX레코드가 있는지 없는지만 확인하고 싶다면
(count (find-mx hostname)) 0)
  true
  false)

(mx-exist? "google.com")
true
(mx-exist? "google3134.com")
false

2017년 12월 3일 일요일

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

clojure design pattern 출처::http://clojure.or.kr/docs/clojure-and-gof-design-patterns.html
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는 엄청난 함수 인 것 같다. 나중에 쓸일이 많을 것 같다.

2017년 11월 30일 목요일

[clojure][design pattern] 클로저 - 커맨드패턴

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


커맨드패턴(Command Pattern)

보면 알겠지만 커맨드(명령)을 login/logout명령으로 나눠서 따로 구현했다.
구현할 때 Command 인터페이스를 구현하여 execute(실행해라!)모든 명령을 이 인터페이스가 있는 녀석으로 구현하면
뭐든 간에 execute만 실행하면 될 것이다.

(defn login [id pass] (println "id: " id ", pass: " pass))

(defn logout [id] (println "logout: " id))

(defn execute [command]
  (do
    (println "logging")
    (command)))

;; 익명인터페이스와 동급

(execute #(login "name" "123"))  

(execute #(logout "idid"))

(defn execute [command & args]
  (apply command args))

(execute login "id" "password") 
apply를 사용하는 이유는 [command & args]에서 args는 리스트형태(?)로 반환하기 때문에 이 리스트 괄호를 깨부시고 안에 들어가서 적용(apply)해야 한다.

(defn wrong-execute [command & args]
  (command args))
(wrong-execute login "id" "password")

ArityException Wrong number of args (1) passed to: user/login  clojure.lang.AFn.throwArity (AFn.java:429)
보는바와 같이 login에는 잘못된 숫자의 매개변수가 들어간 것이다.
아마
(login ["id" "password"])
요래 들어갔을 것이다. 이렇게 실행해보자.
user=> (login ["id" "password"])
ArityException Wrong number of args (1) passed to: user/login  clojure.lang.AFn.throwArity (AFn.java:429)
똑같다 이제 apply를 해보자
user=> (apply login ["id" "password"])
id:  id , pass:  password
nil
아까 apply를 이용하면 괄호들을 깨부수고 안으로 들어(?)간다고 했다.
(apply login ["id" "password"]) == (login "id" "password")
요래 들어가는 것!!! (apply를 꼭 써야 한다.)
============================

이게 왜 중요하다는 거지?

일단 커맨트패턴이 뭔지보자. (자바의 눈으로)

execute()하나를 함께 쓰기 위해서 Command라는 인터페이스를 각각 구현하는 것이다.
그러면 login/logout을 하기 위해 login,logout클래스를 만들고 Command라는 인터페이스를 구현할 것이다.

자 그렇다면 로그인을 위한 Command 인터페이스를 클래스로 구현했다. 그리고 이제 이 클래스를 사용할 것이다.
아니!
정확히는 이 클래스에 있는 Command 안에 execute을 사용할 것이다.

이 커맨드들을 어딘가에 뒀다가 가져오려면 리턴도 되야하고 매개변수로 넣어지기도 해야 할것이다.
클래스는 그럴 수 없다.(뭔줄알고!!) 메소드를 넣을 수도 없다. 인터페이스를 넣는 것이다.
execute를 사용하기 위해!!

클로저의 눈으로 보자.
일단 클로저에서는 함수를 그냥 넘기기 때문에 개념이 아예 다르다.
그냥... 뭔 만들고 넣고 하는게 아니라...
함수를 보내고 받은걸 쓰면 된다.

뭐가 더 좋다가 아니라 이 두 개념을 마음대로 노닐 수 있는 내가 되었으면 좋겠다.

2017년 11월 16일 목요일

[clojure] loop: doseq,dotimes, while, loop, recur 정리

출처 : Clojure Loop

doseq

foreach 문과 비슷하다. 각각의 요소(element)를 돈다(iterate). 중요한 것은 lazy sequence를 강제(force) 평가(evaluation)한다.

(doseq [n (range 10)]
  (println n))
0
1
~
9

dotimes

일반 for문과 비슷하다. 숫자만큼 포문을 돈다.

(dotimes [i 10]
  (println i))
0
1
~
9

while

단순 while문과 비슷

(def a (atom 0))
(while (< @a 10)
  (do
    (println @a)
    (swap! a inc)))
0
1
~
9

loop/recur

loop를 이용하여 재귀를 돌 범위를 정한다. recur을 이용하여 loop의 값을 바꾼다.

(loop [i 0]
  (if (< i 10)
    (do (println i) 
        (recur (inc i)))
    i))
0
1
~
9

defn/recur

함수를 재귀

함수 이름을 부르지 않고 recur을 사용하는 이유는 tail recursion때문이다.

(defn increase [i]
  (if (< i 10)
    (do (println i)
        (recur (inc i)))
    i))
(increase 0)
0
1
~
9

2017년 6월 27일 화요일

[hackerrank][java8][clojure] Simple Array Sum

/* Enter your code here. Read input from STDIN. Print output to STDOUT. Your class should be named Solution. */
Scanner scan = new Scanner(System.in);
int[] i_arr = new int[scan.nextInt()];
for(int i = 0 ; i < i_arr.length; i++) 
  i_arr[i] = scan.nextInt();

System.out.println(Arrays.stream(i_arr).sum());
clojure코드도 한번 써봤다
(use '[clojure.string :only (split trim)])

(let [
  n (Integer/parseInt (read-line))
  arr_t (split (read-line) #"\s+") 
  arr (map #(Integer/parseInt %) arr_t)]
  (println (reduce + arr))   
)
비교를하면
Arrays.stream(i_arr).sum()
이것과
(reduce + arr)
이것의 차이

2017년 2월 19일 일요일

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

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

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


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

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으로 거슬러 올라가서 일이 실행된것!!

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