Lisp tutorial for Pythonista.
Day #3 : Lisp meets web.




                                                          Ransui Iso
                           Strategic Technology Group, X-Listing Co, Ltd.
宿題やった?
今日は Web アプリだよ!

   超基本部分しかやらないけど
モチベーション維持のためにもネタは重要
Allegro Serve ってのがある
  有名な商用処理系の Allegro Common Lisp に
ついてくる Web サーバとちょっとしたフレームワーク
Portable Allegroserve
      ってのを使います
 Allegro serve を他の処理系でも使えるように
移植したやつがあるので、有り難く使わさせて頂く
インストールは簡単

   最初の日にインストールした QuickLisp を使うです


CL­USER> (ql:system­apropos "aserve")
#<SYSTEM aserve / portableaserve­20101006­cvs / quicklisp 2010­12­07>
NIL
CL­USER> (ql:quickload "aserve")

        ****  なんかいろいろでる ****

("aserve")
CL­USER> 
QuickLisp の更新方法

 リポジトリとかたまに更新されるのでチェックしとけ!

CL­USER> (ql:update­client)
Installed version 2010121400 is as new as upstream version 2010121400. 
No update.
T
CL­USER> (ql:update­all­dists)
No update available for "quicklisp 2010­12­07".
NIL
REPL 環境下でテストしてみる

●   とりあえずお約束の Hello World から
CL­USER> (require :aserve)
NIL
CL­USER> (use­package :net.aserve)
T
CL­USER> (use­package :net.html.generator)
T
CL­USER> (start :port 4040)
#<WSERVER port 4040 {1002D47421}>
CL­USER> (defun hello­world (request entity)
           (with­http­response (request entity)
             (with­http­body (request entity)
               (princ "Hello World" *html­stream*))))
HELLO­WORLD
CL­USER> (publish :path "/hello­world"
                  :content­type "text/plain; charset=utf8"
                  :function #'hello­world)

    で、ブラウザで http://localhost:4040/hello­world にアクセス
UTF8 なんだし日本語出るだろ jk

●   .sbclrc に設定もしてるし大丈夫じゃね?
CL­USER> (defun hello­world (request entity)
           (with­http­response (request entity)
             (with­http­body (request entity)
               (princ " 波浪ワールド " *html­stream*))))
UTF8 なんだし日本語出るだろ jk

●   .sbclrc に設定もしてるし大丈夫じゃね?
CL­USER> (defun hello­world (request entity)
           (with­http­response (request entity)
             (with­http­body (request entity)
               (princ " 波浪ワールド " *html­stream*))))




2­aserve­worker: 01/04/11 ­ 17:56:08 ­ while processing command "GET 
/hello­world HTTP/1.1"
got error The value 27874 is not of type (UNSIGNED­BYTE 8).




            多バイト文字文化圏の悲哀!
unicode/byte 文字列変換
●   最終的に byte 列として解釈できりゃ OK らしい
(defun bin­to­str (data)
  (let* ((size (length data))
         (octets (make­array size 
                             :element­type '(unsigned­byte 8)
                             :fill­pointer 0)))
    (dotimes (i size)
      (vector­push (char­code (elt data i)) octets))
    (sb­ext:octets­to­string octets :external­format :utf­8)))

●   ついでなので逆変換もいっとけ
(defun str­to­bin (str)
  (let* ((octets (sb­ext:string­to­octets str 
                         :external­format :utf­8
                         :null­terminate nil))
         (size (length octets))
         (result (make­array size 
                             :element­type 'character
                             :fill­pointer 0)))
    (dotimes (i size)
      (vector­push (code­char (elt octets i)) result))
    result))
また色々新しいの出た!
 例によってザックリと解説
let と let* の違い

●   let は変数束縛が同時に起こる感じ
       ●
(let ((x 10)
      (y 20)
       ●

      (z (+ x y))
       ●

      ●
  ... body ... )
      ●

      ●   z の初期化には x と y が必要だけど、初期化は * 同時 * なので x,y
            ともにまだ存在しない!なので未定義変数参照エラー。

●   let* は変数束縛が逐次的に起こる感じ
(let* ((x 10)
       (y 20)
       (z (+ x y))

  ... body ... )

      ●   こっちは問題ない。この差は多分にコンパイラの都合。
配列の作成

●   make-array で作成する
(make­array  次元指定 or 最大サイズ
              :element­type  データ型
              :initial­contents  初期値
              :fill­pointer  要素の追加位置ポインタを使うか?
              :adjustable  サイズを可変にするか?
                                  他にもオプションあるけど、普通使うのはこんくらい

(setf arr (make­array 3 :element­type 'simple­string
    :initial­contents '("ham" "spam" "egg"))

(serf arr (make­array '(2 2) :element­type 'integer
    :initial­contents '((1 0) (0 1)))

(setf arr (make­array 0 :fill­pointer t :adjustable t))
配列の参照と書き換え

●   単純に参照する場合は aref か elt を使う
(setf arr (make­array 3 :element­type 'integer))
(setf (aref arr 0) 0)
(setf (elt arr 1) 5)
(setf (aref arr 5) 10) ← 当然のことながらエラー
                         
               aref は配列専用で elt はシーケンス汎用。 aref のほうが効率はいいはず。

●   fill-pointer を使うと色々便利
(setf arr (make­array 3 :element­type 'integer :fill­pointer t))
(fill­pointer arr)
(setf (fill­pointer arr) 0)
arr
(vector­push 1 arr)
(vector­push 2 arr)
(vector­push 3 arr)
(vector­push 4 arr) ← 配列の長さを超えたので値は追加できない
                      
          :adjustable を t に指定して vector­push­extend を使えば可変長配列として
          要素ををどんどん追加できる。
dotimes フォーム

●   単純なカウンタ付きループ
(dotimes (var max­value result­value) body)

      ●   変数 var は 0 からスタートして (­ max­value 1) まで回る

      ●   result­value でループが終了したときの dotimes フォームの戻
           り値を指定できる。省略時は nil 。

      ●   body は普通に色々書けばいい。
           –   ループを脱出したい場合は return を使う。この場合 result­value は使われ
                ない。

           –   return と書くのが気持ち悪い人は
               (defmacro break­loop (&body body) `(return ,@body))
               とか定義しておくとちょびっと幸せかも。

           –   ちなみに break って名前はすでに cl:break として使われてて、デバッガへ
                行くとかいう機能になってるですと!こんなイイ名前をもったいない!
他のパッケージ内のシンボルの参照

●   2 つの参照方法がある
     ●   package­name:symbol­name
          –   公開 (export されている ) シンボルを参照する

     ●   package­name::symbol­name
          –   非公開 (export されていない ) シンボルを強制的に参照する




sb-ext パッケージ                SBCL の拡張機能
sb-impl パッケージ               SBCL の内部実装へのインタフェース
さっきまでのプログラムをファイルに書く

●   pastebin 見てね
     ●   http://pastebin.com/7AfPE0GG
     ●   helloworld.lisp  とか名前をつけて保存


●   実行と終了
     ●   sbcl ­­load helloworld.lisp
     ●   REPL が生きてるので (quit) で終了できる

●   リロード
     ●   REPL で (load "helloworld.lisp")
     ●   (publish­pages)
パッケージに関する操作

●   (require :module­name)
      ●   標準パッケージはこれでロードできる。

●   (asdf:oos 'asdf:load­op :module­name)
      ●   インストールされているモジュールをロードする
      ●   QuickLisp でインストールしたモジュールも内部では ASDF の管
           理下にある

●   (use­package :package­name)
      ●   Python で言うところの from package­name import * に似
           ているけど、名前空間の操作しかしない点が異なる。
HTML で出力してみる

●   html マクロを使う
(defun hello­html­world (request entity)
  (with­http­response (request entity)
    (with­http­body (request entity)
      (html (:html
              (:head (:title (u " 波浪ワールド ")))
              (:body 
               (:h1 (u " 波浪ワールド "))
               ((:p :style "color: red;") (u " 赤い文字ですよ "))
               ((:p :style (format nil "color: ~a;" 
                                   (nth (random 5) 
                                        '("red" "blue" 
                                           "green" "purple" "black"))))
                (u " ここはランダムで色が変わるのです ")) ))) )))


    Lisp の中にテンプレートが完全に組み込まれてますよ!
          テンプレの中に処理もそのまま書ける!
HTML マクロの基礎

●   (:tag­name body)
      ●   タグ名はキーワードで指定する
      ●   タグに囲まれる値部分はリストの要素として書く

●   ((:tag­name :attr­name value) body)
      ●   タグに属性をつけたい場合はタグ全体をリスト化して属性名をキー
           にしたキーワード引数風味に書く
      ●   値部分は普通の書き方と同じ

●   テンプレ的機能
      ●   body とか value 部分には「バイナリ文字列」を返す S 式が書ける
           –   便宜的にバイナリ文字列って言ってるよ
           –   いまんとこはマルチバイト文字想定される時は u マクロで囲んどけ。
フォームの取り扱い

●   入力欄は普通に書けばイイ
(defun hello (request entity)
  (with­http­response (request entity)
    (with­http­body (request entity)
      (html (:html
              (:head (:title (u " あいさつ ")))
              (:body
               (:h1 (u " あいさつ "))
               ((:form :method "POST" :action "greeting")
                ((:input :type "text" :name "name" :value ""))
                ((:input :type "submit" :name "greeting­button" 
                              :value "Hi!")) ) )) ))))
フォーム値の取り出し方法

●       request 引数に入ってるので引っ張り出す
(defun get­field­value (request field­name)
  (cdr (assoc field­name (request­query request) :test #'equal)))

    –   assoc 関数:連想リストを検索する
         ●   連想リストとは
             ((key1 value1) (key2 value2) ...)
             形式のリスト。すごく単純な Key Value データの表現。
         ●   線形検索なので当然のことながら効率は良くない。
         ●   が、 hash­table に比べればずっとお手軽。

    –   request­query 関数は
             ((name1 . value1) (name2 . value2) ...)
             形式の dot pair のリストでフォーム値を返す
         ●   dot pair については先週のスライド見てね
テンプレへの値の埋め込み

●   :princ­safe キーワードを使おう
      ●   < > & をエスケープしてくれるので安全
(defun greeting (request entity)
  (let ((name (get­field­value request "name")))
    (if (null name)
        (setf name " 名無しさん ")
        (setf name (bin­to­str name)))

    (with­http­response (request entity)
      (with­http­body (request entity)
        (html (:html
                (:head (:title (u " あいさつ ")))
                (:body
                 (:h1 (u " あいさつ "))
                 (:p (:princ­safe (u (format nil "Hello ~a" name)))) ))
)))))

(format t ... )   : 標準出力ストリームへ書き出す
(format nil ... ) : ストリームへの書き出し無し
お待ちかねの課題です
こんなの作ってね!
            超シンプル掲示板!
書きこまれたデータの保持

●   SQL 系は次回やる予定なので、今回はオンメモリ

(defvar *bbs­datas* nil)

(defun add­new­article (subject body)
  (push (cons subject body) *bbs­datas*))
テンプレートの構成



            書き込みエリア




                      ページ全体
            表示エリア
ページ全体テンプレート

●   こんな感じかな?

(defun page­template ()
  (html
    (:html
      (:head (:title "Tiny BBS"))
      (:body
       (write­section­template)
       (dolist (article *bbs­datas*)
         (view­template article))))))
表示部分のテンプレ

●   こんな感じ

(defun view­template (article)
  (html
    ((:div :style "border: 1px solid black; padding: 2px;")
     ((:div :style "background­color: lightgray; color: black;") 
      (:princ­safe (u (car article))))
     (:pre (:princ­safe (u (cdr article))))) ))
で、あと書かなきゃいけないのは
   書き込み部分のテンプレ
   コントローラ関数 :bbs
コントローラ関数 bbs の作り方のヒント

●   request からフォーム値を取り出す
     ●   書き込みボタンが押されてたら
         –   add-new-article を呼ぶ

     ●   ページ全体テンプレを呼ぶ

     ●   おしまい!
いやー。ヒント出しすぎ
 もう、余裕で作れるでしょ?

Lisp Tutorial for Pythonista : Day 3

  • 1.
    Lisp tutorial forPythonista. Day #3 : Lisp meets web. Ransui Iso Strategic Technology Group, X-Listing Co, Ltd.
  • 2.
  • 3.
    今日は Web アプリだよ! 超基本部分しかやらないけど モチベーション維持のためにもネタは重要
  • 4.
    Allegro Serve ってのがある 有名な商用処理系の Allegro Common Lisp に ついてくる Web サーバとちょっとしたフレームワーク
  • 5.
    Portable Allegroserve ってのを使います Allegro serve を他の処理系でも使えるように 移植したやつがあるので、有り難く使わさせて頂く
  • 6.
    インストールは簡単 最初の日にインストールした QuickLisp を使うです CL­USER> (ql:system­apropos "aserve") #<SYSTEM aserve / portableaserve­20101006­cvs / quicklisp 2010­12­07> NIL CL­USER> (ql:quickload "aserve")         ****  なんかいろいろでる **** ("aserve") CL­USER> 
  • 7.
  • 8.
    REPL 環境下でテストしてみる ● とりあえずお約束の Hello World から CL­USER> (require :aserve) NIL CL­USER> (use­package :net.aserve) T CL­USER> (use­package :net.html.generator) T CL­USER> (start :port 4040) #<WSERVER port 4040 {1002D47421}> CL­USER> (defun hello­world (request entity)            (with­http­response (request entity)              (with­http­body (request entity)                (princ "Hello World" *html­stream*)))) HELLO­WORLD CL­USER> (publish :path "/hello­world"                   :content­type "text/plain; charset=utf8"                   :function #'hello­world) で、ブラウザで http://localhost:4040/hello­world にアクセス
  • 9.
    UTF8 なんだし日本語出るだろ jk ● .sbclrc に設定もしてるし大丈夫じゃね? CL­USER> (defun hello­world (request entity)            (with­http­response (request entity)              (with­http­body (request entity)                (princ " 波浪ワールド " *html­stream*))))
  • 10.
    UTF8 なんだし日本語出るだろ jk ● .sbclrc に設定もしてるし大丈夫じゃね? CL­USER> (defun hello­world (request entity)            (with­http­response (request entity)              (with­http­body (request entity)                (princ " 波浪ワールド " *html­stream*)))) 2­aserve­worker: 01/04/11 ­ 17:56:08 ­ while processing command "GET  /hello­world HTTP/1.1" got error The value 27874 is not of type (UNSIGNED­BYTE 8). 多バイト文字文化圏の悲哀!
  • 11.
    unicode/byte 文字列変換 ● 最終的に byte 列として解釈できりゃ OK らしい (defun bin­to­str (data)   (let* ((size (length data))          (octets (make­array size                               :element­type '(unsigned­byte 8)                              :fill­pointer 0)))     (dotimes (i size)       (vector­push (char­code (elt data i)) octets))     (sb­ext:octets­to­string octets :external­format :utf­8))) ● ついでなので逆変換もいっとけ (defun str­to­bin (str)   (let* ((octets (sb­ext:string­to­octets str        :external­format :utf­8                          :null­terminate nil))          (size (length octets))          (result (make­array size                               :element­type 'character                              :fill­pointer 0)))     (dotimes (i size)       (vector­push (code­char (elt octets i)) result))     result))
  • 12.
  • 13.
    let と let*の違い ● let は変数束縛が同時に起こる感じ ● (let ((x 10)       (y 20) ●       (z (+ x y)) ● ●   ... body ... ) ● ● z の初期化には x と y が必要だけど、初期化は * 同時 * なので x,y ともにまだ存在しない!なので未定義変数参照エラー。 ● let* は変数束縛が逐次的に起こる感じ (let* ((x 10)   (y 20)   (z (+ x y))   ... body ... ) ● こっちは問題ない。この差は多分にコンパイラの都合。
  • 14.
    配列の作成 ● make-array で作成する (make­array  次元指定 or 最大サイズ :element­type  データ型 :initial­contents  初期値 :fill­pointer  要素の追加位置ポインタを使うか? :adjustable  サイズを可変にするか? 他にもオプションあるけど、普通使うのはこんくらい (setf arr (make­array 3 :element­type 'simple­string :initial­contents '("ham" "spam" "egg")) (serf arr (make­array '(2 2) :element­type 'integer :initial­contents '((1 0) (0 1))) (setf arr (make­array 0 :fill­pointer t :adjustable t))
  • 15.
    配列の参照と書き換え ● 単純に参照する場合は aref か elt を使う (setf arr (make­array 3 :element­type 'integer)) (setf (aref arr 0) 0) (setf (elt arr 1) 5) (setf (aref arr 5) 10) ← 当然のことながらエラー   aref は配列専用で elt はシーケンス汎用。 aref のほうが効率はいいはず。 ● fill-pointer を使うと色々便利 (setf arr (make­array 3 :element­type 'integer :fill­pointer t)) (fill­pointer arr) (setf (fill­pointer arr) 0) arr (vector­push 1 arr) (vector­push 2 arr) (vector­push 3 arr) (vector­push 4 arr) ← 配列の長さを超えたので値は追加できない   :adjustable を t に指定して vector­push­extend を使えば可変長配列として 要素ををどんどん追加できる。
  • 16.
    dotimes フォーム ● 単純なカウンタ付きループ (dotimes (var max­value result­value) body) ● 変数 var は 0 からスタートして (­ max­value 1) まで回る ● result­value でループが終了したときの dotimes フォームの戻 り値を指定できる。省略時は nil 。 ● body は普通に色々書けばいい。 – ループを脱出したい場合は return を使う。この場合 result­value は使われ ない。 – return と書くのが気持ち悪い人は (defmacro break­loop (&body body) `(return ,@body)) とか定義しておくとちょびっと幸せかも。 – ちなみに break って名前はすでに cl:break として使われてて、デバッガへ 行くとかいう機能になってるですと!こんなイイ名前をもったいない!
  • 17.
    他のパッケージ内のシンボルの参照 ● 2 つの参照方法がある ● package­name:symbol­name – 公開 (export されている ) シンボルを参照する ● package­name::symbol­name – 非公開 (export されていない ) シンボルを強制的に参照する sb-ext パッケージ SBCL の拡張機能 sb-impl パッケージ SBCL の内部実装へのインタフェース
  • 18.
    さっきまでのプログラムをファイルに書く ● pastebin 見てね ● http://pastebin.com/7AfPE0GG ● helloworld.lisp  とか名前をつけて保存 ● 実行と終了 ● sbcl ­­load helloworld.lisp ● REPL が生きてるので (quit) で終了できる ● リロード ● REPL で (load "helloworld.lisp") ● (publish­pages)
  • 19.
    パッケージに関する操作 ● (require :module­name) ● 標準パッケージはこれでロードできる。 ● (asdf:oos 'asdf:load­op :module­name) ● インストールされているモジュールをロードする ● QuickLisp でインストールしたモジュールも内部では ASDF の管 理下にある ● (use­package :package­name) ● Python で言うところの from package­name import * に似 ているけど、名前空間の操作しかしない点が異なる。
  • 20.
    HTML で出力してみる ● html マクロを使う (defun hello­html­world (request entity)   (with­http­response (request entity)     (with­http­body (request entity)       (html (:html               (:head (:title (u " 波浪ワールド ")))               (:body                 (:h1 (u " 波浪ワールド "))                ((:p :style "color: red;") (u " 赤い文字ですよ "))                ((:p :style (format nil "color: ~a;"                                     (nth (random 5)                                          '("red" "blue"    "green" "purple" "black"))))                 (u " ここはランダムで色が変わるのです ")) ))) ))) Lisp の中にテンプレートが完全に組み込まれてますよ! テンプレの中に処理もそのまま書ける!
  • 21.
    HTML マクロの基礎 ● (:tag­name body) ● タグ名はキーワードで指定する ● タグに囲まれる値部分はリストの要素として書く ● ((:tag­name :attr­name value) body) ● タグに属性をつけたい場合はタグ全体をリスト化して属性名をキー にしたキーワード引数風味に書く ● 値部分は普通の書き方と同じ ● テンプレ的機能 ● body とか value 部分には「バイナリ文字列」を返す S 式が書ける – 便宜的にバイナリ文字列って言ってるよ – いまんとこはマルチバイト文字想定される時は u マクロで囲んどけ。
  • 22.
    フォームの取り扱い ● 入力欄は普通に書けばイイ (defun hello (request entity)   (with­http­response (request entity)     (with­http­body (request entity)       (html (:html               (:head (:title (u " あいさつ ")))               (:body                (:h1 (u " あいさつ "))                ((:form :method "POST" :action "greeting")                 ((:input :type "text" :name "name" :value ""))                 ((:input :type "submit" :name "greeting­button"  :value "Hi!")) ) )) ))))
  • 23.
    フォーム値の取り出し方法 ● request 引数に入ってるので引っ張り出す (defun get­field­value (request field­name)   (cdr (assoc field­name (request­query request) :test #'equal))) – assoc 関数:連想リストを検索する ● 連想リストとは ((key1 value1) (key2 value2) ...) 形式のリスト。すごく単純な Key Value データの表現。 ● 線形検索なので当然のことながら効率は良くない。 ● が、 hash­table に比べればずっとお手軽。 – request­query 関数は ((name1 . value1) (name2 . value2) ...) 形式の dot pair のリストでフォーム値を返す ● dot pair については先週のスライド見てね
  • 24.
    テンプレへの値の埋め込み ● :princ­safe キーワードを使おう ● < > & をエスケープしてくれるので安全 (defun greeting (request entity)   (let ((name (get­field­value request "name")))     (if (null name)         (setf name " 名無しさん ")         (setf name (bin­to­str name)))     (with­http­response (request entity)       (with­http­body (request entity)         (html (:html                 (:head (:title (u " あいさつ ")))                 (:body                  (:h1 (u " あいさつ "))                  (:p (:princ­safe (u (format nil "Hello ~a" name)))) )) ))))) (format t ... ) : 標準出力ストリームへ書き出す (format nil ... ) : ストリームへの書き出し無し
  • 25.
  • 26.
    こんなの作ってね! 超シンプル掲示板!
  • 27.
    書きこまれたデータの保持 ● SQL 系は次回やる予定なので、今回はオンメモリ (defvar *bbs­datas* nil) (defun add­new­article (subject body)   (push (cons subject body) *bbs­datas*))
  • 28.
    テンプレートの構成 書き込みエリア ページ全体 表示エリア
  • 29.
    ページ全体テンプレート ● こんな感じかな? (defun page­template ()   (html     (:html       (:head (:title "Tiny BBS"))       (:body        (write­section­template)        (dolist (article *bbs­datas*)          (view­template article))))))
  • 30.
    表示部分のテンプレ ● こんな感じ (defun view­template (article)   (html     ((:div :style "border: 1px solid black; padding: 2px;")      ((:div :style "background­color: lightgray; color: black;")        (:princ­safe (u (car article))))      (:pre (:princ­safe (u (cdr article))))) ))
  • 31.
    で、あと書かなきゃいけないのは 書き込み部分のテンプレ コントローラ関数 :bbs
  • 32.
    コントローラ関数 bbs の作り方のヒント ● request からフォーム値を取り出す ● 書き込みボタンが押されてたら – add-new-article を呼ぶ ● ページ全体テンプレを呼ぶ ● おしまい!
  • 33.