티스토리 툴바


세상에 아직도 이곳이 남아 있군요. 누군가, 나 자신조차도  찾아 오지 않았는데 말입니다.

아무튼. 벌써 2011년 하고도 일월이 반이나 지나갔습니다.

저는 아직도 lisp를 찝쩝대고 있습니다. 그 동안 뭐 많이 왔다 갔다 하였습니다. 하다못해 프롤로그, 헤스겔도 조금 찔러보고 말입니다. 그리고는 다시 common lisp으로 돌아 왔습니다.

현재 제 실력, lisp 실력으로 말하자면 아직도 초보입니다. 끈기 있게 계속 하지 않았으니 실력이 늘 턱도 없고, 더구나 책을 읽다 덮는 순간에 모든 것을 다 잊어버리고 마는 요즈음의 제 기억력이 더 이상의 진보가 없습니다.

역설적으로는 참 제가 끈기가 있기는 있습니다. 매번 앞에서만 얼쩡거리면서도 아직도 lisp책을 붙들고 있는 것을 보면 말입니다.

요즈음은 Land of Lisp 와 고전 On Lisp을 붙들고 있습니다.

언제나 끝장을 볼지…

2011.01.17

'주절주절' 카테고리의 다른 글

참 끈기 있네 or 없네.  (0) 2011/01/17
Clojure를 시작하였습니다.  (0) 2009/07/10
생각보다는 쉽지 않네요!  (0) 2009/06/19
새로운 시각  (0) 2009/05/25
Practical Common Lisp 책 3장을 번역(공부) 중입니다.  (3) 2009/05/16
시작합니다.  (2) 2009/05/13
Posted by SimonYim

댓글을 달아 주세요

대화식 자료입력

Clojure 2009/09/17 11:15

이번 주제는 대화식 자료입력입니다. PCL의 저자는 add-record 함수가 잘 동작하지만 이런한 입력방식은 너무 lispy하다고 말을 합니다. 고로 보다 사용자에게 친숙하기 위하여 대화형 입력방식을 소개합니다. 대화형 입력방식이란 화면에 prompt로 무엇을 입력하여야 하는지를 보여주고 사용자가 그 내용을 입력하면 그 자료가 저장이 되어야 합니다. 물론 아직까지는 저장된다는 의미가 메모리내에 db에 저장되는 것을 뜻합니다.

그런데 처음에 정의를 하지 않고 지나온 부분이 있습니다. c계열로 프로그램을 하거나 실제 데이타베이스를 구축한다면 이렇게 잊어버리고 지나가는 경우가 없겠습니다만…

이 데이타베이스에서 사용하는 데이타필드는 Title, Artist, Rating, Ripped인데 항목만을 명확히 정의하였지 이들의 속성은 정의하지 않고 두리뭉실 넘어 왔습니다. 확실하게 속성을 정의하자면 Title과 Artist는 문자열고 Rating은 숫자, 그리고 Ripped는 진리값 즉 Boolean으로 하겠습니다.

아무튼, 화면에 prompt를 출력하고 입력을 받는 함수는 common lisp에서는 아래와 같습니다.

(defun prompt-read (prompt)

  (format *query-io* “~a;” prompt)

  (force-output *query-io*)

  (read-line *query-io*))

Clojure 코드도 대동소이합니다.

(defn prompt-read [prompt]
    (print (format "%s: " prompt))
    (flush)
    (read-line))

함수정의 첫번째 줄에는 함수이름 prompt-read와 인자로 [prompt]를 정의하였고, 두번째줄은 화면에 prompt를 표시하여 주는 것임은 당근입니다. 세번째줄, (flush) 는 common lisp의 (force-output *query-io*)와 같이, 현재 *out* 값의  outoutput stream을 “비워”줍니다. 그리고 네번째줄은 입력값을 읽어주는 것은 당연하겠지요.

이 함수를 make-cd의 각각 필드를 위한 입력값으로 사용하면 됩니다만, 문제는 이 함수를 사용하면 당연히 단수 문자열를 반환하여 주기 때문에, 이를 Rating을 위하여는 숫자로, Ripped를 위하여는 boolean값으로 변환하여주는 함수가 필요합니다.

우선 Rating을 위한 입력된 문자열을 숫자로 변화하는 common lisp 최종 코드는 다음과 같습니다.

(or ( parse-integer (prompt-read “Rating”) :junk-allowed t) 0)

Clojure 코드가 이번에는 조금 더 복잡하게 보입니다.

(defn parse-integer [str]
    (try (Integer/parseInt str)
            (catch NumberFormatException nfe 0)))

첫번째 줄을 설명하면 이글을 읽으시는 분들의 수준을 무시하는 것 같아서 생략합니다. 두번째줄 괄호안부터 설명합니다. common lisp에서와 같이 integer로 변환하는 함수같은데 조금 이상합니다. 대문자가 섞여 쓰인 것이 약간은 java 냄새가 납니다. 그런데 “.”가 아닌 “/”가 중간에 있군요. 결론은 java를 호출하였습니다.clojure에 관심을 가지셨다면 대부분 java를 아실터이니

Integer.parseInt(str)

라고 표시한다면 친숙하시겠지요? 예 자바를 불러 사용한 예입니다. Clojure 장점중에 하나겠지요. 자바에서는 바로 위와 같이 표시합니다만 clojure에서의 정식 표기법은 아래와 같습니다

(. Integer parseInt str)

그런데 이 정식표기법보다는 약식으로 사용하는 것이 “조금” 간편하고, 보다 명확하게 어디까지가 클래스명과 맴버명이고 어떤것이 인자인지 알수 있는 것이 바로 "syntactic sugar”형 (저는 그냥 “간이형식”이라 하겠습니다.) 인 아래와 같습니다.

(Integer/parseInt str)

그런데 str이 정수값으로 변환할 수없는 기타문자나 기호인경우에는 에러가 발생합니다. 이 에러를 처리하는 방법이, 잘 아시는 try – catch 입니다. 그래서 결국은

(try (Integer/parseInt str)
            (catch NumberFormatException nfe 0)))

즉 에러가 발생하면, 문자나 기호인 경우에는 0를 반환하라는 것입니다.  자세한 설명은 생략합니다. 끝!

다음은 Ripped를 위하여 (사실은 y/n만을 입력받기 위한 모든 곳을 위하여)  입력을 y, Y, n, N 만을 허용하고 이를 clojure의 boolean값인 true / false로 반화하는 함수를 정의 하겠습니다. common lisp에서는 이기능을 y-or-n-p 함수로 제공합니다. 우리는 이것과 같은 함수를 정의하여 사용하겠습니다.

(defn y-or-n-p [prompt]
    (= "y"
        (loop []
            (or
                (re-matches #"[yn]" (.toLowerCase (prompt-read prompt)))
                (recur)))))

첫째줄은 y-or-n-p 함수를 정의합니다. 인자는 화면상에 prompt를 띄울 문자열을 받아들이고, 반환값은 Boolean 값입니다. 둘째줄은 (loop []…) 반환값이 “y” 와 동일하면 true 를 아니면 false를 반환하는 조건식이고, 세째줄에는 그 조건식의 두번째 인자값을 줄 (loop []…) 표현식이군요. clojure의 loop는 (recur)를 만나면 되돌아 옵니다만, 그렇지 않으면 종료됩니다. 물론 []안에는 바인딩하려 loop안에서 사용할 인자를 사용할 수 있습니다만, 이곳에서는 바인딩하여 사용할 인자가 없기에 공란입니다. (or…)안의  첫번째 표현식은 중첩된 가장 안쪽의 (prompt-read prompt)는 당연히 위에서 정의한 대화식입력 함수입니다. 즉 입력을 받아 이것을 (.toLowerCase…)로 대문자인 경우 소문자로 변경합니다. (.)는 자바함수를 call 한다는 의미이고 toLowerCase는 자바를 사용하신 분들께 설명할 필요가 없겠지요? 다음은 (re-matcjes …)인데 이것은 정규표현식을 사용하여 주어진, 여기서는 입력된 문자열에 yn이 있는 지를 확인하여 있다면 그것을 반환합니다. 만일 없다면 거짓값이 반환되니 (or …)안에 있으므로 다음 표현식이 평가되는데, 다음 표현식이 (recur)이니, loop가 되풀이 됨을 뜻합니다. 즉 y,Y,n 또는 N이 입력되지 않았다면 다시 입력을 기다리는 무한반복이 됩니다.

그러면 이제 준비가 다 되었습니다. 대화형으로 cd 정보를 입력하는 함수를 만들어 봅시다.

(defn prompt-for-cd []
    (make-cd
            (prompt-read "Title")
            (prompt-read "Artist")
            (parse-integer (prompt-read "Rating"))
            (y-or-n-p "Ripped [y/n]")))

 

설명할 곳이 없군요. 이 함수를 add-record 함수에 사용하여 메모리 db에 입력하고, 사용자가 반복하여 여러 cd정보를 입력하게 할 수 있는 loop- 되풀이 기능을 넣어 add-cds 함수를 아래와 같이 정의합니다.

(defn add-cds []
    (loop []
        (add-record (prompt-for-cd))
        (if (y-or-n-p "Another? [y/n]") (recur) )))

 

(loop [] ….)표현식을 이해하였다면 별 설명이 필요없습니다.

그러면 한번 실행을 하여 볼까요?

1:3 user=> (add-cds)
Title: Rockin'the Suburbs
Artist: Ben Folds
Rating: 6
Ripped [y/n]: y
Another? [y/n]: y
Title: Give Us a Break
Artist: Limpopo
Rating: 10
Ripped [y/n]: y
Another? [y/n]: y
Title: Lyle Lovett
Artist: Lyle Lovett
Rating: 9
Ripped [y/n]: y
Another? [y/n]: n
nil

1:19 user=> (dump-db)
     title: Lyle Lovett
    artist: Lyle Lovett
    rating: 9
    ripped: true

     title: Home
    artist: Dixie Chicks
    rating: 9
    ripped: true

     title: Roses
    artist: kathy Mattea
    rating: 7
    ripped: true

     title: Rockin'the Suburbs
    artist: Ben Folds
    rating: 6
    ripped: true

     title: Fly
    artist: Dixie Chicks
    rating: 8
    ripped: true

     title: Give Us a Break
    artist: Limpopo
    rating: 10
    ripped: true

nil
1:21 user=>

 

하하하…잘 동작합니다. 화면의 프롬프트가 1:3에서 3장의 cd정보를 입력후에는 갑자기 1:19로 뛰는 군요. 아마 프롬프트입력의 각라인이 계산되는 모양입니다.  아무튼 이렇게 힘들게 입력한 정보를 “정말” 잘 보관하기 위하여 화일에 저장하는 것은 다음 주제로 하겠습니다.

계속 진행될 실용 간단한 데이타베이스를 위하여 계속 필요한 코드는 아래와 같습니다.

[[code format="lisp"]]
(defstruct cd-field :title :artist :rating :ripped)
(defn make-cd [title artist rating ripped]
    (struct cd-field title artist rating ripped))
(def db (ref #{}))
(defn add-record [cd]
   (dosync (alter db conj cd )))

(defn dump-db []
    (doseq [cd @db]
        (doseq [[key value] cd ]
            (print (format "%10s: %s \n" (name key) value)))
        (println)))

(defn prompt-read [prompt]
    (print (format "%s: " prompt))
    (flush)
    (read-line))

(defn parse-integer [str]
    (try (Integer/parseInt str)
            (catch NumberFormatException nfe 0)))

(defn y-or-n-p [prompt]
    (= "y"
        (loop []
            (or
                (re-matches #"[yn]" (.toLowerCase (prompt-read prompt)))
                (recur)))))

(defn prompt-for-cd []
    (make-cd
            (prompt-read "Title")
            (prompt-read "Artist")
            (parse-integer (prompt-read "Rating"))
            (y-or-n-p "Ripped [y/n]")))
(defn add-cds []
    (loop []
        (add-record (prompt-for-cd))
        (if (y-or-n-p "Another? [y/n]") (recur) )))


[[code]]

'Clojure' 카테고리의 다른 글

대화식 자료입력  (2) 2009/09/17
데이타 덤프하기  (0) 2009/09/02
A Simple Database for Clojure(2)  (0) 2009/08/22
Practical a Simple Database for Clojure (1)  (0) 2009/08/14
Posted by SimonYim

댓글을 달아 주세요

  1. 박영식 2009/12/06 23:19  댓글주소  수정/삭제  댓글쓰기

    아, 저도 해 봐야 겠네요.

데이타 덤프하기

Clojure 2009/09/02 20:39

자 이제 메모리내에 데이타베이스를 만들었고, 세개의 레코드를 넣어 보았습니다.
입력한 데이타가 맞는지 알아보려면 데이타베이스의 내용을 보아야 겠지요. 그래서 지난번 마지막에 확인한 내용이

1:6 user=> @db
#{{:title "Home", :artist "Dixie Chicks", :rating 9, :ripped true} {:title "Roses", :artist "kathy Mattea", :rating 7, :ripped true} {:title "Fly", :artist "Dixie Chicks", :rating 8, :ripped true}}

였습니다. 보기가 눈에 거슬리니 다음 단계는 당연히 이 출력을 보기좋게 하는 함수를 정의하는 것입니다.

PCL에서는 아래와 같았습니다.

(defun dump-db ()
  (dolist (cd *db*)
    (format t "~{~a:~10t~a~%~}~%" cd)))

물론 dolist를 사용하지 않고, format만의 루프기능으로 다음과 같이 할수있는 리스피한 방식도...

(defun dump-db ()
  (format t "~{~{~a:~10t~a~%~}~%~}" *db*))

자 그럼 clojure방식을 보도록 합시다.

(defn dump-db []
   (doseq [cd @db]
      (doseq [[key value] cd ]
      (print (format "%10s: %s \n" (name key) value)))
      (println)))

설명할 필요가 있는 줄이 있을까요?
없다고 느끼신 분은 다음에 뵙겠습니다. 안녕!

아무튼 설명을 하겠습니다. 저를 위하여...

첫줄은 함수 정의입니다. 물론 인자가 없어도 []를 넣어 줍니다.
두번째 줄, doseq는 CL의 dolist 기능과 같습니다. doseq의 형식은 아래와 같습니다.

(doseq bindings & body)

고로 첫번째 doseq의 바인딩은 db에 있는 한 레코드를 cd에 바인딩하고 body부분을 실행합니다. 실행이 끝나고나서 db안에 내용이 남아 있다면 새로이 cd에 다음 레코드를 바인딩하고 body부분을 다시 실행합니다. 이런 동작을 db안에 제일 마지막까지 실행후에 동작을 종료합니다. 동작이 CL의 dolist와 같습니다만 이름이 dolist에서 doseq가 된것은 눈치로 보건데 리스트뿐만이 아니라 clojure에서 SEQ-ABLE, 즉 시퀀스기능을 사용할 수 있는 모든 형식을 다룰수 있기에 그런 것 같군요.

두번째 doseq는 cd 차체도 맵형식의 seq-able이니, 맵안의 하나하나의 필드를 키워드는 key에, 해당값은 value에 할당하여 줍니다. 그리고는 프린트하면 끝...

당연히 프린트시에 줄맞쳐 양식에 맞게하기 위하여 format 문이 필요합니다. format문의 형식은 CL형식이 아니라 java형식입니다. PCL의 저자는 CL의 format문 양식이 다른 언어에 비하여 비슷하다고 하였는데, java나 다른 언어에서 사용하는 수준이 훨씬 간단하지요? 저 개인적인 생각입니다. 아무튼 설명 생략합니다. 필요하신분은 java관련 책이나 싸이트에서...(죄송!)

그런데 format문안의 변수명 key앞에 하나가 더 붙는군요. (name key)...뭐 보나마나 key의 내용은 키워드이기 때문에 앞에 :이 붙어 있습니다. 이 콜론을 떼어내고 이름만을 반환하여 주는 것이 바로 name이군요!

다음줄의 plintln은 줄바꿈이라고 설명할 필요가 없지요?

자, 그럼 메모리 db안의 세개의 레코드를 덤프하여 보겠습니다.

user=> (dump-db)
     title: Home
    artist: Dixie Chicks
    rating: 9
    ripped: true

     title: Roses
    artist: kathy Mattea
    rating: 7
    ripped: true

     title: Fly
    artist: Dixie Chicks
    rating: 8
    ripped: true

nil

쉽게 넘어갑니다.

'Clojure' 카테고리의 다른 글

대화식 자료입력  (2) 2009/09/17
데이타 덤프하기  (0) 2009/09/02
A Simple Database for Clojure(2)  (0) 2009/08/22
Practical a Simple Database for Clojure (1)  (0) 2009/08/14
Posted by SimonYim

댓글을 달아 주세요