Gauche > Archives > 2012/06/01

2012/06/01 00:32:29 UTCenami
#
modgauche.texi, 「EOFを返したジェネレータさえも繰り替えして」,は「繰り返して」,でしょうか.
2012/06/01 00:36:37 UTCshiro
#
fixed. mahalo >enami
2012/06/01 01:25:17 UTCshiro
#
あれ、sourceforge.net 落ちてる?
2012/06/01 01:31:48 UTCshiro
#
直った
2012/06/01 01:34:08 UTC(び)
#
うはー。恥ずかし。
2012/06/01 01:36:35 UTCshiro
#
あ、いや、びぜんさんのパッチを当てたあと私もちょこまかいじったので私が誤変換入れたかも。
2012/06/01 01:38:48 UTC(び)
#
いかにもことえりっぽい誤変換ですw
2012/06/01 06:00:13 UTCyamasushi
#
http-getの簡易版があればいいなと思います。入力はuri文字列で出力はhtml bodyのようなものです。
#
; uriをhttp-get系に渡せるようなパラメータにする
(define (uri->param uri
			:key
				(pass values ) ; 成功時の継続 ホスト、パスの2つ
				(fail (^(uri) (values #f #f))) ; 失敗時の継続
				)
	(receive (_ _ host port path query _) (uri-parse uri)		
		(if host
			(pass
				(if port  (format "~a:~a" host port)  host )
				(if path
					(if query 
						(format "~a?~a" path query)
						path)
					"/" ) )
			(fail uri)
		)))

; uri指定、http-getで取得する。
(define (simple-http-get uri 
		:key 
			; これでいいのかどうか。とにかくbodyを返すのが標準に。
			(pass-success      (^(status header body) body) ) ; 成功時の継続
			;
			; http status 1xx 時の継続
			; とりあえず、つけた。
			(pass-informational(^(status header body) #t) ) 
			;
			; http status 3xx 時の継続 (http-getでredirectの処理をするので実は冗長なのだがとりあえずつけた)
			(pass-redirection  (^(status header body) #t) ) 
			; 
			;------------------------------------------------------------
			; 失敗時はとにかく#fを返すのが標準。調べたいときにcpsを使う。
			(fail-uri          (^(uri) #f) ) ; uri解析失敗時の継続 
			(fail-client       (^(status header body) #f) ) ; http status 4xx 時の継続 
			(fail-server       (^(status header body) #f) ) ; http status 5xx 時の継続 
			(fail-http-get     (^(status header body) #f) ) ; http-get失敗時の継続 
		)
	(receive (param-host param-path) (uri->param uri)
		;(format (standard-error-port) "param-host=~a , param-path=~a\n" param-host param-path)
		(if param-host
			(receive (status header body) (http-get param-host param-path) 
				(case (string-ref status 0) 
					[ (#\1) (pass-informational status header body) ]
					[ (#\2) (pass-success       status header body) ]
					[ (#\3) (pass-redirection   status header body) ]
					[ (#\4) (fail-client        status header body) ]
					[ (#\5) (fail-server        status header body) ]
					[ else  (fail-http-get      status header body) ]
				) )
			(fail-uri uri) 
		);if
	);receive 
)
#
キーワード引数を使ってCPSを使うことで、省略時には簡易動作を行い、チェックをしたいときには継続を指定するというアイデアなんですが。
#
ステータスコードごとの動作定義には自信がありません(汗。
2012/06/01 06:05:07 UTCshiro
#
プランとしては、rfc.httpは中間レイヤーで、その上層にhttpとかftpとかいろんなものと統一的に通信できるopen-urlみたいなものを作ろうと考えています。
2012/06/01 06:10:58 UTCyamasushi
#
そうだったのですか。たとえばsxml変換を組み合わせやすいようなものがあるといいなと。uri文字列からcomposeでつないでいくような感じで。
#
uri文字列→html→sxml、uri文字列→xml→xmlの流れが統一的にできればいいですね。
#
(修正)uri文字列→xml→sxmlでした。
#
html→sxmlにはHtmlPragを使わせてもらっています。http://www.neilvandyke.org/htmlprag/
#
xml→sxmlも同じように変換できれば、統一感があるなと。
#
; xml文字列をssaxでsxmlにする
(define (string-xml->sxml xml :optional (ns '()) )
	(call-with-input-string xml (cut ssax:xml->sxml <> ns ) ) )
2012/06/01 06:20:39 UTCshiro
#
xmlは巨大になることも多いのでいちいち文字列で取り回すのは重そうです。open-urlがポートを返してそれをssax:xml->sxmlにつなぐのでいいんじゃないでしょうか。open-urlがあればcall-with-open-urlも作れるので(call-with-open-url url (cut ssax:xml->sxml <> '())) 等。
#
htmlとxmlのパーズの統一については、他にも統一するものがあればやりたいですが、2種類くらいならまあどちらも外部ライブラリだしその都度使い分ければいいかなあとか思っちゃいます。
#
sxmlは何人かの手で書き継がれているのでライブラリとしてのAPIの統一性がいまいちなところはありますね。なかなか覚えられない。とはいえあれだけのものを再発明するのもなあ。
2012/06/01 06:25:12 UTCyamasushi
#
なるほど。難しいものですね。htmlもポートから読むようなライブラリがあればいいということでしょうか。
#
あ、HtmlPragでも読んでいるのかも(汗。
2012/06/01 06:28:35 UTCshiro
#
Neil Van Dykeさんはhtmlpragの後継のSXML/xexpというのを書いてるのか http://www.neilvandyke.org/racket-xexp/ racket専用なのかな。LGPLだからどっちにせよGaucheにバンドルはできないけど。
2012/06/01 06:45:05 UTCyamasushi
#
html,xml,jsonを同じ様式でアクセスできるようになると表現の可能性が出るような気がします。
2012/06/01 08:04:57 UTCshiro
#
jsonと{xml,html}は本来の構造が違うけどどうなんでしょ。パーズするAPIを統一しとくってのはまあありかな。というかデフォルトでポートを取るだけでいいと思うけど。ssax:xml->sxmlのns引数が必須なのが色々面倒の元なのか。
2012/06/01 08:53:40 UTCyamasushi
#
リソースID → 生リソース → 中間形式 → 処理
#
のようなプロセスを抽象化しやすくなると思いました。
#
プロセス間のパラメータには多値を許すということにすると、いろいろおもしろいと思います。
2012/06/01 08:56:18 UTCshiro
#
でもjsonとxmlでは「処理」の部分は共有できませんよね? 表記のパターンの問題なら、今既にあるもので出来ないことって何でしょう?
2012/06/01 08:59:24 UTCyamasushi
#
json、xmlからalistを生成して、それを使って処理するようなことを考えていました。いまあるものでも表現できるのですが、処理全体を定型化しておいて、個別の形式についてはカスタム化していくような流れです。
2012/06/01 09:00:57 UTCshiro
#
もうちょい具体的にコード例を示せます? イメージがつかめないので
2012/06/01 09:01:56 UTCyamasushi
#
; リソースID(uriなど)が指すデータを読んで、アイテムリストに変換する手続きを返す。
; アイテム、ヘッダを多値で返す。(ヘッダはないときもあるのでアイテム優先の順序)
(define (read-and-map$
          :key
          get-raw     ; id  ---> res    元データを読む生データを返す
          raw->items  ; res ---> items  生データからアイテムリストに変換
          (raw->header #f )   ; res ---> header 生データからヘッダ抽出 ... 省略可
          (item-map    #f )   ; 各アイテムを変換
          (item-filter #f )   ; 変換後のフィルタ
          (item-peek   #f )   ; 変換前のアイテムを覗く
          ;
          (peek-id (^ arg ) ) ; idを覗くため
          )
  (.$
    ;(^(x y) (print y) (exit))
    (pa$ apply values )
    ;(^x (print x) (exit) )
    (list-pack$
      (.$
        ; 変換後のフィルタ
        (^ arg ;(print x) (exit)
          (if item-filter
            (if (car arg) (filter item-filter (car arg)) #f)
            (apply values arg) ) )

        ; 各アイテムを変換
        (^ arg ;(print x) (exit)
          (if item-map
            (if (car arg) (map item-map (car arg) ) #f)
            (apply values arg) ) )

        (^ arg ;(print x) (exit)
          (when (and (car arg) item-peek ) ; (変換前のアイテムを覗く)
            (for-each item-peek (car arg) )
            ) (apply values arg) )

        (^ arg ;(print x) (exit)
          (if (car arg) (apply raw->items arg) #f)) ; 生データをアイテムリストに変換
        )
      (^ arg
        (if raw->header
          (if (car arg) (apply raw->header arg) #f)
          #f ) )
      )
    (^ arg ; 生データ取得
      (if (car arg) (apply get-raw arg) #f)) 
    (peek$ peek-id) ; (idを覗く)
    )
  )
#
; 手続き列に値を通して、指定手続きpackerで固める手続きを返す
; ((pack$ f g h) x) -----> (f (g x) (h x) )
(define (pack$ packer . op)
	(^ arg
		(apply packer
			(fold-right (^(f k) (cons (apply f arg) k) ) '() op) ) ) 
)

; 手続き列に値を通して、リストにするような手続きを返す
; ( (list-pack$ f g h) x ) ---> ( (f x) (g x) (h x) )
; ( (list-pack$ car cdr) '(1 2 3) ) ----> ( 1 (2 3))
(define list-pack$ (pa$ pack$ list ))
#
; .$ の途中にはさんで中身をのぞくため
(define (peek$ peeker)
	(^ arg
		(apply peeker arg) ;; xを覗く
		(apply values arg) ;; xをそのまま返す
	))
#
; OpenSearchやGoogle Apiのような、RESTなアクセスの抽象化をするyielder生成
; 終了時はyieldにeof-objectを渡す。(generatorを生成可能)
(define (opendata-make-yielder
          :key
          start-param  ; 最初のパラメータ(get-raw-startに渡す)
          (get-raw-start #f) ; 最初の生データを取得する。省略時はget-rawと同じ
          get-raw      ; 生データを取得する
          raw->chunk   ; 生データを塊データに変換する。
          chunk->items ; 塊データをアイテムリストに変換する。
          chunk->next-param ; 塊データを次のパラメータに変換する。(これをget-rawに渡す)
          )
  (^ (yield)
    (let loop [
               [i 0]
               [raw ((if get-raw-start get-raw-start get-raw) start-param)]]
      (if (not raw) 
        (yield (eof-object)) ; end
        (begin
          ;#?=raw
          (let* [
                 [chunk (raw->chunk raw)]
                 [items (chunk->items chunk)]
                 [next-param (chunk->next-param i chunk)]
                 ]
            (for-each yield items)
            (if (not next-param)
              (yield (eof-object)) ; end
              (loop (+ i 1) (get-raw next-param)))
            )
          )
        )
      )
    ))
#
yielderというのはyieldを呼んでeofで締める内部イテレータのことを言っています。
2012/06/01 09:06:15 UTCとおる。
#
他の言語だとだいたい libxml の DOM API が使えるので、その上に簡単なパターンマッチャと遅延評価でツリーを構築する関数を作ってそれを使うことが多いです。SXML みたいなのが理想で、それを少しでもまねようとしている感じなんですけど。
2012/06/01 09:07:40 UTCshiro
#
コンビネータプログラミングですね。なんかでももうちょい簡単に書けそうな気もするんだけどどうかなあ。
#
モナド風に組んだ方がコンパクトにならないかな。
2012/06/01 09:20:11 UTCshiro
#
read-and-map$とか、意図はわかるんですが、多分こういうカスタマイズパラメータがたくさんある手続きってどれがどういう機能だったか忘れちゃうんですよね。3年前に書いたコードを読む時にマニュアルをひっくり返すかread-and-map$の実装を読むハメになる。それよりは、パイプラインでつなげてゆくこういう処理をべたでコンパクトに書けるツールを整備しておくほうがいいんじゃないかな、という気はします。
#
ここに見えてるパターンは、(^ args (..... (apply f args) ....)) っていう関数を数珠つなぎにしてるんですが、こういう似たパターンの関数を数珠つなぎにするのってモナドでうまく書けることが多いんだけど、この場合はどうだろう。受け取るargsを使う式がネストの奥にあるのが面倒の元のように見える。
2012/06/01 09:49:08 UTCyamasushi
#
コンビネータプログラミングというものがどういうものなのかを知らないのです。(冷汗 wilikiにあるんでしょうか?
#
モナド風コードとか、和風でおいしそうな語感ですが、どのようなコードなんだろうかと、いまHaskell本をひっくりかえしています。
2012/06/01 09:59:13 UTCyamasushi
#
なぜ、このようなパラメータの多い形式にしたかといいますと、キーワード名そのものが与えるパラメータに意味を与える点と、パラメータが並列に並ぶのでちょこっと改造するときに見やすいという点です。http://yamasushi.hatenablog.com/entry/20120506/1336257782
#
他の言語のことはよくわからないのですが、xmlは特別扱いなんでしょうか?
2012/06/01 11:01:33 UTCyamasushi
#
あ、末尾に$をつけている点なのですが、composeでつなげていくのが「コンビネータプログラミング」なのだろうと判断して、composeの要素になりうる手続きに$をつけているのですが、クロージャを返すなら皆$をつけたほうがいいのかどうかと迷うことが多いです。
2012/06/01 11:41:14 UTCyamasushi
#
ググってみるとschemeでモナドを使うという記事がいくつかみつかりましたので、勉強してみます。
2012/06/01 23:28:38 UTCshiro
#
yamasushiさん、上のコード見てみたのですがやっぱりわからないことがあります。read-and-map$ の各構成要素の関数が
#
(^ arg ;(print x) (exit)
          (if item-filter
            (if (car arg) (filter item-filter (car arg)) #f)
            (apply values arg) ) )
#
こんなパターンになってますが、(if (car arg) ...) の式の返す値は常に1つであるのに対し、(apply values arg)の側は不定ですよね。composeは多値をリストで受けるので動くといえば動くのですが、返す値の個数が不定な関数ってちょっと気味悪いですね。(不定長引数を取る関数と対になってると考えれば避けるべきものではないかもしれませんが…)
#
で、わからないことというのは、どうせ不定長で値を受け渡す=リストにいちいちパックして、また分解する、のなら、最初からリストで値を流してゆけばいいんじゃないかなあということです。最初の値が#fであることを失敗とみなしているようだけれど、それも「有効な値はリストで流す」ということなら「失敗したら#fを流す」でいいと思いますし。そうするとMaybeモナドのパターンも使えます。
#
あと、関係無いですがpack$とlist-pack$は逆にした方が (list-pack$を最初に定義して、それを使ってpack$を定義する) 方が素直じゃないかなあ? apply する時には既にリストは出来てるので apply list ... は意味が無いような。
#
(define (list-pack$ . fs) (^ xs (map (cut apply <> xs) fs)))
(define (pack$ p . fs) ($ p $* (apply list-pack$ fs) $*))
#
とか? (どうしても関数合成でやりたいなら、ですが…)