(new Hatena).blog() https://reinyannyan.hatenadiary.org/ http://blogs.law.harvard.edu/tech/rss Hatena::Blog radikoスケジューラー https://reinyannyan.hatenadiary.org/entry/20101209/p1 <p>聴きたい<a class="keyword" href="http://d.hatena.ne.jp/keyword/radiko">radiko</a>の番組を聴き逃さないために、時間になったら自動的に番組を再生してくれるプログラムを作ってみました。</p><p>再生にはブラウザを使用するので、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a>で番組表<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を叩いて検索、番組データを<a class="keyword" href="http://d.hatena.ne.jp/keyword/JSON">JSON</a>化してブラウザに渡す、という流れになります。</p><p>ブラウザと<a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a>の通信に関して、<a class="keyword" href="http://d.hatena.ne.jp/keyword/JSONP">JSONP</a>を使うかどうか悩んだんですが、Racket(PLT <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a>)のテンプレート・システムでHTMLデータを生成する方法を選びました。</p><br /> <p>メイン関数</p> <pre class="code" data-lang="" data-unlink>(fun (radiko info) (aand (filter-map (find-progs info) ((sxpath &#39;(radiko stations station)) (timetable))) (progs.json it) (serve it)))</pre><pre class="code" data-lang="" data-unlink>(radiko &#39;((pfm &#34;山里亮太&#34;)))</pre><p>のように呼び出します。pfm(performer)やtitle等の検索項目の後に文字列のリストを並べると、OR検索が行われるようにしています。</p><p>ちなみに、個々の番組情報は以下のような<a class="keyword" href="http://d.hatena.ne.jp/keyword/XML">XML</a>データです。</p> <pre class="code" data-lang="" data-unlink>(prog (@ (dur &#34;7200&#34;) (ft &#34;20101202010000&#34;) (ftl &#34;2500&#34;) (to &#34;20101202030000&#34;) (tol &#34;2700&#34;)) (title &#34;&lt;![CDATA[JUNK〜山里亮太の不毛な議論〜]]&gt;&#34;) (pfm &#34;&lt;![CDATA[山里亮太(南海キャンディーズ)]]&gt;&#34;) (url &#34;&lt;![CDATA[http://abc1008.com]]&gt;&#34;))</pre><p>TBSの番組なのにテレ朝系でネットされているという...。ラジオって自由で良いですね。</p><br /> <p>検索関数</p> <pre class="code" data-lang="" data-unlink>(fun (find-progs info station) (aand (filter (finder info) ((sxpath &#39;(prog)) station)) (cons (station-id station) it))) (fun (finder info) (apply orf (filter-map (fn ((cons key ref)) (aand (lookup key info) (matcher ref it))) `((pfm . ,prog-pfm) (title . ,prog-title) (desc . ,prog-desc) (info . ,prog-info))))) (fun (matcher ref target prog) (set rx (regexp (string-join #\| target))) (aand (ref prog) (regexp-match? rx it)))</pre><p>finder関数によって、ユーザーが与えた全ての検索条件が1つの検索関数にまとめられます。orf:</p> <pre class="code" data-lang="" data-unlink>(fun (orf . fns) (if (ormap [eqv? (procedure-arity _) 1] fns) (fn (x) (ormap [_ x] fns)) (fn args ((afn (fns) (and fns (or (apply (car fns) args) (self (cdr fns))))) fns))))</pre><p><br /> <a class="keyword" href="http://d.hatena.ne.jp/keyword/JSON">JSON</a>化関数</p> <pre class="code" data-lang="" data-unlink>(fun (progs.json data) (aand (append-map (fn ((cons stid progs)) (map (fn (prog) (make-immutable-hasheq `((station . ,stid) (start . ,(prog-start prog)) (end . ,(prog-end prog)) (pfm . ,(prog-pfm prog)) (title . ,(prog-title prog))))) progs)) data) (filter [&lt; (current-seconds) (hash-ref _ &#39;end)] it) (sort [&lt; (hash-ref _1 &#39;start) (hash-ref _2 &#39;start)] it) (call-with-output-string [write-json it _])))</pre><p>既に放送が終わっているものは削除したり、放送順に並べたりもしています。</p><br /> <p>HTML生成+ブラウザ起動関数</p> <pre class="code" data-lang="" data-unlink>(fun (serve progs) (send-url/contents (include-template &#34;radiko.tmpl&#34;)))</pre><p>include-templateというのはweb-server/templatesというライブラリのマクロで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>時にテンプレート・ファイルの解析を行います。</p><p>テンプレート中に、この例の場合@progsというシンボルがあると、スコープ内のprogsという変数の中身と置き換えられる仕組みです。</p><p>net/sendurlライブラリのsend-url/contents関数は、HTML文字列をファイル化してデフォルトのブラウザに開かせる、というものです。</p><br /> <p>その他</p> <pre class="code" data-lang="" data-unlink>(fun (api path) (format &#34;http://radiko.jp/~a&#34; path)) (fun (area) (get-url (api &#34;area&#34;) [regexp-match1 #rx&#34;class=\&#34;(.+?)\&#34;&#34; _])) (fun (epg when) (sxml:document (api (format &#34;epg/newepg/epgapi.php?area_id=~a&amp;mode=~a&#34; (area) when)))) (fun (timetable) (epg &#34;today&#34;)) (fun (nowplaying) (epg &#34;now&#34;)) (set cdata (regexp-match1 #rx&#34;^&lt;!\\[CDATA\\[(.+?)\\]\\]&gt;$&#34;)) (fun (string.path path) [aand ((sxpath path) _) (sxml:string it) (or (cdata it) it)]) (set station-id (string.path &#39;(@ id))) (set prog-pfm (string.path &#39;(pfm))) (set prog-title (string.path &#39;(title))) (set prog-desc (string.path &#39;(desc))) (set prog-info (string.path &#39;(info))) (fun (prog-time param prog) (time-second (date-&gt;time-utc (string-&gt;date ((string.path `(@ ,param)) prog) &#34;~Y~m~d~H~M&#34;)))) (set prog-start (prog-time &#39;ft)) (set prog-end (prog-time &#39;to))</pre><p>いろいろと俺々なライブラリに依存しまくっていて申し訳ないんですが、ポータブルなコードよりもコンセプトの提示の方に関心がある、ということの表れなのでご容赦ください。</p><br /> <p>テンプレート:<a href="http://eririn.no.land.to/radiko.tmpl">radiko.tmpl</a></p> Thu, 09 Dec 2010 00:00:00 +0900 hatenablog://entry/17680117127059120701 Scheme 今、日本でお気に入りされているYoutube動画 https://reinyannyan.hatenadiary.org/entry/20101105/p1 <p>を一覧できる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>を書いてみました。<br /> <a href="http://eririn.no.land.to/jyoulike.html">jYoulike</a><br /> <a href="http://eririn.no.land.to/jyoulike.html.txt">&#x30C0;&#x30A6;&#x30F3;&#x30ED;&#x30FC;&#x30C9;</a></p><br /> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Twitter">Twitter</a>の検索<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を用いて、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Youtube">Youtube</a>のお気に入りボタンと連携して投稿されたツイートを検索し、サムネイル化します。</p><p>サムネイルをクリックするとインラインで動画が開きます。</p><p>常に最新の、画面に表示しきれるだけのサムネイルしか表示しない仕組みになっています。</p><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Windows">Windows</a>の方は、HTMLファイルをダウンロードしてデスクトップの壁紙にするのがおすすめの使い方です(あ、その場合アイコンは非表示にした方が良いかもしれません)。</p> Fri, 05 Nov 2010 00:00:00 +0900 hatenablog://entry/17680117127059120969 JavaScript Scheme で定数定義 https://reinyannyan.hatenadiary.org/entry/20100624/p1 <p>define がそろそろ長くて面倒になってきたので (遅いですか?)、こういうマクロを作ってみました。</p> <pre class="code" data-lang="" data-unlink>(define-syntax set (syntax-rules () ((set id x) (define id x))))</pre><p>これならタイプするのも簡単です。</p><p>Racket であれば</p> <pre class="code" data-lang="" data-unlink>(define-syntax set (make-rename-transformer #&#39;define))</pre><p>で define の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%A4%A5%EA%A5%A2%A5%B9">エイリアス</a>を作ることも可能なんですが、変数定義用の限定版ということで、上のでも良いかなと思います。</p><p>発展形として、set で導入された id を再定義できなくする方法も考えてみました。</p> <pre class="code" data-lang="" data-unlink>(define-syntax set (syntax-rules () ((set id e) (begin (define id~ e) (define-syntax id (syntax-id-rules (set!) ((set! id _) (error &#39;id &#34;is not modifiable.&#34;)) ((id . es) (id~ . es)) (id id~)))))))</pre><pre class="code" data-lang="" data-unlink>&gt; (set x 1) &gt; x 1 &gt; (set! x 2) x: is not modifiable.</pre><p>syntax-id-rules 便利!</p> Thu, 24 Jun 2010 00:00:00 +0900 hatenablog://entry/17680117127059121249 Scheme Scheme から Racket へ https://reinyannyan.hatenadiary.org/entry/20100604/p1 <p>ユーザーなのに気づくのが遅れたんですが、PLT <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> の名称が Racket に変更されるそうです。</p><p><a href="http://racket-lang.org/new-name.html">http://racket-lang.org/new-name.html</a><br /> <a href="http://news.ycombinator.com/item?id=1221374">http://news.ycombinator.com/item?id=1221374</a></p><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E1%A1%BC%A5%EA%A5%F3%A5%B0%A5%EA%A5%B9%A5%C8">メーリングリスト</a>で時々 Racket という単語を見かけてはいたんですが、特に気にしてなかったんですよねー。何気なくググってみて真相を知り、一瞬びっくりしたんですが、すぐに「良いじゃん」という気持ちになりました。</p><p>今後私は Schemer ではなく Racketeer と呼ばれることになります(笑)。</p> Fri, 04 Jun 2010 00:00:00 +0900 hatenablog://entry/17680117127059121471 MPEG-4、ツリー探索、autovivification https://reinyannyan.hatenadiary.org/entry/20100524/p1 <p>近ごろ動画や音声データの読み書きを <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> でしているんですが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/MPEG-4">MPEG-4</a> の処理が個人的にかなり楽しかったので少し書きます。</p><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/MPEG-4">MPEG-4</a> のデータはこのようなツリー構造になっており、お馴染みのツリー探索のテクニックが活躍しそうな雰囲気です。</p> <pre class="code" data-lang="" data-unlink>(define structure &#39;((moov . (mvhd trak udta iods)) (trak . (mdia tkhd)) (mdia . (minf mdhd hdlr)) (minf . (stbl vmhd smhd dinf)) (dinf . (dref)) (stbl . (stsd stsc stts ctts stco co64 stss stsz)) (udta . (meta cprt)) (meta . (ilst id32))))</pre><p>(全てを網羅したものではありません。例えば子ノード (box) を持たないトッ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%EC%A5%D9">プレベ</a>ル box はこの図には入っていません。)</p><p>MP4 のツリー構造にはルートノードというものは無く、トッ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%EC%A5%D9">プレベ</a>ルの box が複数並んでいる構成のため、box を構造体 (下記) で表し、それらをリストでまとめるという方法で扱っていきます。</p> <pre class="code" data-lang="" data-unlink>(define-struct box (size type ext-size data))</pre><p>size は 32 ビットの符号無し整数で、box のサイズがそれを超える場合は size を 1 とし、64 ビットの ext-size フィールドを使う仕様になっています。</p><p>data にはバイト列か、サブツリーとして box のリストを入れます。なお、メディア本体の場合はバイト列に変換せず、入力ポートをそのまま置くこととします。</p><p>ツリーの読み込みは前述のように、トッ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%EC%A5%D9">プレベ</a>ル box をリストで集めるだけです。</p> <pre class="code" data-lang="" data-unlink>(fun (mp4-boxes in) (let loop ((t &#39;())) (receive (x done) (read-box in) ((if done reverse loop) (cons x t)))))</pre><p>動画サイトの MP4 を扱う前提なので、メディア本体のデータは最後に来ることを想定しています。この順序は必ず保たなければいけません。</p><p>個々の box は以下で読み込みます。</p> <pre class="code" data-lang="" data-unlink>(fun (read-box in) (receive (size type ext-size) (box-head in) (if (eq? type &#39;mdat) (values (make-box size type ext-size in) #t) (values (let ((dsize (data-size size ext-size))) (make-box size type ext-size (if (eq? type &#39;meta) (meta-tag dsize in) (parent? type) (box-kids dsize in) (read-bytes dsize in)))) #f)))) (fun (box-kids size in) (let loop ((size~ 0) (kids &#39;())) (if (= size~ size) (reverse kids) (receive (kid done) (read-box in) ;; `done&#39; must not be #t here (loop (+ (box-size~ kid) size~) (cons kid kids))))))</pre><p>parent? で子 box を持つタイプかどうかを判別します。最初の structure の左のコラムがそうです。</p> <pre class="code" data-lang="" data-unlink>(define parent? [memq _ (map car structure)])</pre><p>parent? は読み込みの際に必要ですが、ツリーの検索や更新をする場合には逆に、子からその親を調べる関数が必要になることに気づきました。</p> <pre class="code" data-lang="" data-unlink>(fun (parent type) (ormap (fn ((cons par kids)) (and (memq type kids) par)) structure))</pre><p>type を子に持つ親 box のシンボルを返します。#f が返った場合はトッ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%EC%A5%D9">プレベ</a>ル box だと判断します。</p><br /> <p>ここからが本題で、読み込んだツリー構造を更新する関数を作ります。タイトルを付けたりカバーアートを埋め込んだりする際に使うものです。</p> <pre class="code" data-lang="" data-unlink>(fun (insert-box box boxes) (let ((par (parent (box-type box)))) (if par (if (find-tree (is-box? par) boxes) (map-tree (fn (x) (if (is-box? par x) (update-box x (append (filter (negate (is-box? (box-type box))) (box-data x)) (list box))))) boxes) (insert-box (new-box par (if (eq? par &#39;meta) (list null-space box) (list box))) boxes)) (cons box boxes)))) ;; Helper functions (fun (fold-tree f s t) (let loop ((t t) (s s)) (aif (f t s) it (box? t) (loop (box-data t) s) (pair? t) (loop (cdr t) (loop (car t) s)) s))) (fun (find-tree p? t) (prompt (fold-tree (lambda (x _) (if (p? x) (control k x))) #f t))) (define weight (fold-tree (lambda (x s) (if (box? x) (+ (box-head-size x) (weight (box-data x)) s) (meta-box? x) (+ (meta-box-size x) s) (meta-data? x) (+ (meta-data-size x) s) (bytes? x) (+ (bytes-length x) s))) 0)) (define ext-size? [&gt;= _ (expt 2 32)]) (define (update-box abox data) (let* ((size (+ (weight data) 8)) (ext? (ext-size? size))) (struct-copy box abox (size (if ext? 1 size)) (ext-size (and ext? (+ size 8))) (data data)))) (fun (new-box type data) ;; using update-box to auto compute the size (update-box (make-box 0 type #f #f) data))</pre><p>box をツリー内の狙った位置に投入したいということで、まず親を探して (find-tree)、もしあればその中に追加する (map-tree, update-box)、無ければ親 box を作る、という流れです。</p><p>妙なこだわりで、"!" の付く構文を一切使いたくないというのがあるせいで、find-tree と map-tree で同じ検索を2度してしまうことになるのが残念です。</p><p>ここで思い出しました。必要無い時はコンスしない map の定義方法です。</p> <pre class="code" data-lang="" data-unlink>(fun (map-tree f t) (aif (f t) it (pair? t) ;; To cons less often. See: ;; http://okmij.org/ftp/Scheme/zipper-in-scheme.txt (let ((a (car t)) (d (cdr t))) (let ((a~ (map-tree f a)) (d~ (map-tree f d))) (if (and (eq? a a~) (eq? d d~)) t (cons a~ d~)))) (box? t) (let* ((x (box-data t)) (x~ (map-tree f x))) (if (eq? x x~) t (update-box t x~))) t))</pre><p>これに基づいて変更したバージョンが以下です。</p> <pre class="code" data-lang="" data-unlink>(fun (insert-box box boxes) (let ((par (parent (box-type box)))) (if par (let ((boxes~ (map-tree (fn (x) (if (is-box? par x) (update-box x (append (filter (negate (is-box? (box-type box))) (box-data x)) (list box))))) boxes))) (if (eq? boxes boxes~) ;not modified == par box not found (insert-box (new-box par (if (eq? par &#39;meta) (list null-space box) (list box))) boxes) boxes~)) (cons box boxes))))</pre><p>find-tree を無くすことができました。</p><p>試しに使ってみましょう。空の box のリストに box を挿入しようとすると、</p> <pre class="code" data-lang="" data-unlink>&gt; (pp (insert-box (new-box &#39;ilst #f) &#39;())) (#(struct:box 36 moov #f (#(struct:box 28 udta #f (#(struct:box 20 meta #f (#&#34;\0\0\0\0&#34; #(struct:box 0 ilst #f #f))))))))</pre><p>となり、自動的にツリー構造が生成されていることが分かります。(サイズ計算もばっちり)</p><p>検索して挿入する場合もこの通り、</p> <pre class="code" data-lang="" data-unlink>&gt; (pp (insert-box (new-box &#39;ilst #f) (insert-box (new-box &#39;cprt #f) &#39;()))) (#(struct:box 44 moov #f (#(struct:box 36 udta #f (#(struct:box 8 cprt #f #f) #(struct:box 20 meta #f (#&#34;\0\0\0\0&#34; #(struct:box 8 ilst #f #f))))))))</pre><p>うまく行っています。</p><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%CB%A5%B3%A5%CB%A5%B3%C6%B0%B2%E8">ニコニコ動画</a>の MP4 には udta の下に cprt (コピーライト) の box しか無いので、メタ情報 (ilst box) を追加するにはこの自動生成の機能が欠かせません。</p> Mon, 24 May 2010 00:00:00 +0900 hatenablog://entry/17680117127059121601 Scheme 多値アクセス構文 https://reinyannyan.hatenadiary.org/entry/20100523/p1 <p>ML(や<a class="keyword" href="http://d.hatena.ne.jp/keyword/Haskell">Haskell</a>)の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DE">プログラマ</a>がタプルを使うような文脈でSchemerは多値を使うと思うんですが、値が一つだけ欲しいという場合に多値はちょっと不便なんですよね。</p><p>MLだとfstやsndというアクセサが使えるんですが、それに相当するものが無いなと思って、作ってみました。</p> <pre class="code" data-lang="" data-unlink>(define (pos x) (or (string-&gt;number (substring (symbol-&gt;string (syntax-e x)) 1)) 1)) (define (positional-args var) (let loop ((n 1) (l (list (cons (pos var) var))) (r &#39;())) (cond ((null? l) (let ((vars (reverse (map cdr r)))) (append vars (gensym)))) ((= (caar l) n) (loop (add1 n) (cdr l) (cons (car l) r))) (else (loop n (cons (cons n (gensym)) l) r))))) (define-syntax (def-valref stx) (syntax-case stx () ((def-valref ref _n) (with-syntax ((args (positional-args #&#39;_n))) (syntax/loc stx (begin (define ref~ (lambda args _n)) (define-syntax ref (syntax-id-rules () ((ref v) (call-with-values (lambda () v) ref~)) (ref ref~))) (provide ref))))))) (def-valref fst _1) (def-valref snd _2)</pre><p>positional-argsは、例えば _3 というシンボルを与えると (g97 g98 _3 . g99) というリストを返す関数です。これをそのままラムダのパラメータ位置に置けば、目的の位置引数に変数をバインドできるわけです。</p><br /> <p>利用例です。このように、関数を置けない所で関数呼び出しっぽいスタイルで値を取り出すことができます。</p> <pre class="code" data-lang="" data-unlink>&gt; (fst (values 1 2 3)) 1</pre><p>こういう使い方も可能です。</p> <pre class="code" data-lang="" data-unlink>&gt; (call-with-values (lambda () (values 1 2 3)) fst) 1</pre><p>[追記]</p><p>後から気づきましたが、PLT <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> の compose は多値を返す関数にも対応しているので、</p> <pre class="code" data-lang="" data-unlink>(map (compose fst multi-valued-func) lst)</pre><p>という使い方もできますね。</p><p>なお、fst はあくまでもマクロであることを強調しておきます。</p> Sun, 23 May 2010 00:00:00 +0900 hatenablog://entry/17680117127059121895 Scheme PLT SchemeのREPLで直前の値を参照できるようにする https://reinyannyan.hatenadiary.org/entry/20100328/p1 <p>CLの処理系にも似たようなのがあると思いますが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Haskell">Haskell</a>のGHCiで、直前に評価された値にitでアクセスできるというのがあったので、真似してみました。</p> <pre class="code" data-lang="" data-unlink>#lang scheme/base (provide it) (define it~ &#39;()) (define eval~ (current-eval)) (define-syntax it (syntax-id-rules (set!) ((set! it _) (error &#39;it &#34;is not modifiable.&#34;)) ((it e ...) ((car it~) e ...)) (it (apply values it~)))) (current-eval (lambda (x) (call-with-values (lambda () (eval~ x)) (lambda results (unless (or (null? results) ;; retain the old value if the new one was void (and (null? (cdr results)) (void? (car results)))) (set! it~ results)) (apply values results)))))</pre><p>デフォルトのevalをオーバーライドし、evalの結果 (results) を保存する処理を挟んでいます。resultsは多値をリスト化したものなので、それを改めて多値に変換するマクロとしてitを定義してあります。</p><p>itの定義にはsyntax-id-rulesを使っています。変数のように扱えるマクロを作るマクロです。変数的な振る舞いは3番目の節で定義しています。</p><p>関数適用の形でも使えるようにするには2番目の定義が必要です。順番が重要です。これを後に持ってきてしまうと関数適用の形式が捕捉できなくなるので注意しましょう。</p><p>あと細かいですが、GHCiではlet構文で変数束縛を行った後でも古いitが残るみたいなので、それも真似てみました (PLTではdefineの結果voidが返る、という挙動に基づいています)。</p><br /> <p>実行例</p> <pre class="code" data-lang="" data-unlink>&gt; (+ 1 2) 3 &gt; it 3 &gt; (+ it 4) 7 &gt; list #&lt;procedure:list&gt; &gt; (it 1 2 3) (1 2 3) &gt; (thread (lambda () ((lambda (f) (f f)) (lambda (f) (display &#34;.&#34;) (f f))))) #&lt;thread&gt; &gt; .....................................(kill-thread it) ..........&gt;</pre><p>スレッドなど、readできない形式の値を<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B0%A5%ED%A1%BC%A5%D0%A5%EB%CA%D1%BF%F4">グローバル変数</a>にバインドし忘れた場合でもアクセスが可能になるので便利ですね。</p><p>なお、こういったREPLの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B3%C8%C4%A5%B5%A1%C7%BD">拡張機能</a>は次のように<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%DE%A5%F3%A5%C9%A5%E9%A5%A4%A5%F3">コマンドライン</a>引数で自動的に読み込まれるようにすると良いです。</p> <pre class="code" data-lang="" data-unlink>(setq scheme-program-name &#34;mzscheme -t /path/to/init/file -i&#34;)</pre> Sun, 28 Mar 2010 00:00:00 +0900 hatenablog://entry/17680117127059122144 Scheme Clojure風の無名関数構文 https://reinyannyan.hatenadiary.org/entry/20100217/p1 <p>Arcには角括弧で無名関数を作る構文があります。</p><p>例:</p> <pre class="code" data-lang="" data-unlink>&gt; (map [if _ (+ 1 _) 1] &#39;(1 -1 nil)) (2 0 1)</pre><p>便利そうなのでマクロで模倣して喜んでいたんですが、1引数の関数しか作れないのが不便に感じることもありました。</p><p>で、最近<a class="keyword" href="http://d.hatena.ne.jp/keyword/Clojure">Clojure</a>にはその多変数版みたいなものがあるらしいのを知り、取り入れてみることにしました。</p><p>方針としては、従来のアンダースコアのみの識別子に加えて、その後に数字が付いているものも有効な変数と見なすようにします。</p> <pre class="code" data-lang="" data-unlink>(define (underscore? stx) (and (identifier? stx) (let ((s (symbol-&gt;string (syntax-e stx)))) (and (positive? (string-length s)) (char=? (string-ref s 0) #\_) (let ((t (substring s 1))) (or (zero? (string-length t)) ; _ (string-&gt;number t))))))) ; _[0-9]+</pre><p>そして、マクロ展開時にコードからアンダースコア変数を抽出し、必要となる位置引数を調べます。この際、最初の引数を _1 に対応させることとします。</p><p>これらを数字の順番に並べてラムダのパラメータにすれば完成、となるはずです。</p><p>その作業を行うのがこちらです。</p> <pre class="code" data-lang="" data-unlink>(define (pos x) (or (string-&gt;number (substring (symbol-&gt;string (syntax-e x)) 1)) 1)) (define (positional-vars vars) (let loop ((n 1) (l (sort (map (lambda (v) (cons (pos v) v)) vars) (lambda (x y) (&lt; (car x) (car y))))) (r &#39;())) (cond ((null? l) ;; append rest parameter (append (reverse (map cdr r)) (gensym))) ((= (caar l) n) (loop (add1 n) (cdr l) (cons (car l) r))) (else (loop n (cons (cons n (gensym)) l) r)))))</pre><p>アンダースコア変数が例えば _3 だけ、ということもあり得るので、抜けている変数は gensym で補うようにします。</p><p>positional-vars の返り値はドット対で、そのままラムダのパラメータ位置に埋め込まれます。</p><p>あまり普段はしない使い方だと思いますが、append の最後の引数が非リストの場合はドット・リストが作られます。ドットの後のシンボルがちょうど残余パラメータの役割を果たしてくれるわけです。</p><p>これにより、例えばこんな関数が定義できるようになります。</p> <pre class="code" data-lang="" data-unlink>(define second [values _2]) ; values as identity</pre><pre class="code" data-lang="" data-unlink>&gt; (second &#39;a &#39;b &#39;c) b &gt; (call-with-values (lambda () (values &#39;a &#39;b &#39;c)) second) b</pre><p>有りそうで無かった感じがしません?</p><br /> <p>続き。マクロ本体</p> <pre class="code" data-lang="" data-unlink>(define-syntax (make-brackets-funny stx) (syntax-case stx (as) ((make-brackets-funny (orig as name)) (with-syntax ((tmp (string-&gt;symbol (format &#34;tmp-~a&#34; (syntax-e #&#39;orig))))) (syntax/loc stx (begin (define-syntax (tmp stx~) (syntax-case stx~ () ((tmp . expr) (bracketed? stx~) (with-syntax ((vars (positional-vars (uniq-ids (filter underscore? (stx-filter (lambda (x) (or (identifier? x) (bracketed? x))) (unbracket #&#39;expr))))))) (syntax/loc stx~ (lambda vars (orig . expr))))) ((tmp . expr) (syntax/loc stx~ (orig . expr))))) (provide (rename-out (tmp name))))))) ((make-brackets-funny orig) (syntax/loc stx (make-brackets-funny (orig as orig)))) ((make-brackets-funny orig . rest) (syntax/loc stx (begin (make-brackets-funny orig) (make-brackets-funny . rest))))))</pre><p>色々省略しましたが、大枠は以上です。</p><p>アンダースコア変数を取り出す際に、ネストされている角括弧構文は探索しないようにするのがポイントです (stx-filterの部分)。</p> <pre class="code" data-lang="" data-unlink>(make-brackets-funny #%app if ...)</pre><p>のようにして利用します。Arcのようにリーダーに手を入れているわけではないので (そうすべきなのかも知れませんが)、無名関数化したい構文ごとに構文を再定義する必要があるんです。</p><br /> <p>ファイル:<br /> <a href="http://eririn.no.land.to/scheme/arcfun.ss">arcfun.ss</a> <a href="http://eririn.no.land.to/scheme/mlfun.ss">mlfun.ss</a></p> Wed, 17 Feb 2010 00:00:00 +0900 hatenablog://entry/17680117127059122336 Scheme 環境を汚さないrequire https://reinyannyan.hatenadiary.org/entry/20100107/p1 <p>トッ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%EC%A5%D9">プレベ</a>ルで作業をする時に、どうしてもライブラリを利用したくなりますが、その時にrequireで幾つもロードしていると、何となくREPLを汚してしまってるような気分になります。</p><p>実際それで困ることはまず無いんですが、たまに基本構文のセマンティクスが変わって戸惑うこともあったりします。</p><p>そこで思い出したのが<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BE%C1%B0%B6%F5%B4%D6">名前空間</a>です。</p><p>PLT <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a>では<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BE%C1%B0%B6%F5%B4%D6">名前空間</a>が第一級オブジェクトなので、式評価の環境を自在に作ったり変更したりできるんです。</p><p>例えば</p> <pre class="code" data-lang="" data-unlink>&gt; (current-namespace (make-empty-namespace))</pre><p>とするとREPLの環境がまっさらな状態になる、という具合です (関数適用すらできなくなります)。</p><p>これを利用して、ローカルな環境の中だけでライブラリを読み込む構文を作ってみました。</p> <pre class="code" data-lang="" data-unlink>(begin-for-syntax (define req-specs (append &#39;(for-meta for-syntax for-template for-label just-meta) &#39;(only prefix all-except prefix-all-except rename))) (define (req-spec? spec) (or (module-path? spec) (and (pair? spec) (memq (car spec) req-specs))))) (define-syntax (with-require stx) (syntax-case stx () ((with-require mod . body) (req-spec? (syntax-&gt;datum #&#39;mod)) #&#39;(with-require (mod) . body)) ((with-require (mod ...) . body) (andmap req-spec? (syntax-&gt;datum #&#39;(mod ...))) #&#39;(parameterize ((current-namespace (make-base-namespace))) (for-each namespace-require &#39;(mod ...)) (eval &#39;(begin . body))))))</pre><p>例</p> <pre class="code" data-lang="" data-unlink>&gt; (with-require &#34;prelude.ss&#34; (aif 0 (add1 it))) 1 &gt; aif reference to undefined identifier: aif</pre><p><br /> 参考:<br /> <a href="http://docs.plt-scheme.org/guide/eval.html">http://docs.plt-scheme.org/guide/eval.html</a><br /> <a href="http://docs.plt-scheme.org/guide/mk-namespace.html">http://docs.plt-scheme.org/guide/mk-namespace.html</a></p> Thu, 07 Jan 2010 00:00:00 +0900 hatenablog://entry/17680117127059122550 Scheme Purely Functional Red-Black Tree https://reinyannyan.hatenadiary.org/entry/20091203/p1 <p>ちょっと思い立って作ってみました。</p><p>最初は<a href="http://programmingpraxis.com/2009/10/02/red-black-trees/">Programming Praxis</a>で示されている実装から出発したんですが、結果的には<a href="http://www.cs.kent.ac.uk/people/staff/smk/redblack/rb.html">Haskell&#x7248;</a>の実装に近い形に変化していきました。</p><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%EB%A5%B4%A5%EA%A5%BA%A5%E0">アルゴリズム</a>等の解説は<a href="http://en.wikipedia.org/wiki/Red-black_tree">Wikipedia</a>に譲ることにして、ここではマクロを活用して煩雑なパターンマッチを<a class="keyword" href="http://d.hatena.ne.jp/keyword/Haskell">Haskell</a>っぽく簡潔に行う方法など、テクニック的な話題や応用例を紹介したいと思います。</p> <div class="section"> <h4>ツリー構文</h4> <p>まず、ツリーの構造を定義します。</p> <pre class="code" data-lang="" data-unlink>(define-struct tree (color left node right)) (define empty (make-tree &#39;black #f (cons #f #f) #f)) (define (empty? t) (eq? t empty))</pre><p>キーと値は cons で纏めて node として一体的に扱うことにします (最初は別々にしていたんですが、あまりにもコーディングが煩雑になったので…)。</p><p>構造体を利用する際に便利なのが match 構文です。</p> <pre class="code" data-lang="" data-unlink>&gt; (match (make-tree &#39;black empty &#39;(1 . #f) empty) ((struct tree (&#39;black (? empty?) x (? empty?))) &#34;This is a black tree with both subtrees empty.&#34;)) &#34;This is a black tree with both subtrees empty.&#34;</pre><p>このように、アクセサ関数を使わずとも色の判定ができたりして、コードが簡潔になります。</p><p>でも、もっと簡潔にすることはできないでしょうか?</p><p>例えば</p> <pre class="code" data-lang="" data-unlink>(match (T B E &#39;(1 . #f) E) ((T B E x E) &#34;This is a black tree with both subtrees empty.&#34;))</pre><p>のように書ければ素晴らしいですよね。</p><p>define-match-expander というマクロを使うと、それが可能になります。</p> <pre class="code" data-lang="" data-unlink>(define-match-expander T (lambda (stx) (syntax-case stx () ((T C a x b) (syntax/loc stx (struct tree (C a x b)))))) (lambda (stx) (syntax-case stx () ((T C a x b) (syntax/loc stx (make-tree C a x b))))))</pre><p>最初のラムダがマッチ構文内での展開形、2番目は式として呼ばれた場合の展開形の定義です。</p><p>色</p> <pre class="code" data-lang="" data-unlink>(define-match-expander R (lambda (stx) (syntax-case stx () (R (syntax &#39;red)))) (lambda (stx) (syntax-case stx () (R (syntax &#39;red))))) (define-match-expander B (lambda (stx) (syntax-case stx () (B (syntax &#39;black)))) (lambda (stx) (syntax-case stx () (B (syntax &#39;black)))))</pre><p>空ツリー</p> <pre class="code" data-lang="" data-unlink>(define-match-expander E (lambda (stx) (syntax-case stx () (E (syntax/loc stx (? empty?))))) (lambda (stx) (syntax-case stx () (E (syntax/loc stx empty)))))</pre><p>R、B、E が、マクロでありながら値のように扱えることを確認してみましょう。</p> <pre class="code" data-lang="" data-unlink>&gt; (list R B E) (red black #s(tree black #f (#f . #f) #f))</pre><p>構文の準備ができると、例えば赤い木を黒くする関数が、このように書けるようになります。</p> <pre class="code" data-lang="" data-unlink>(fun (blacken (and t (T B _ _ _))) t \| (blacken (T _ a x b)) (T B a x b))</pre><p>パターンマッチとツリーの生成とが同じ形式になっていますね (2行目)。</p><p>1行目は、木が既に黒ければそのまま返すというパターンです。and が ML や <a class="keyword" href="http://d.hatena.ne.jp/keyword/Haskell">Haskell</a> の as パターンと同じ意味で使えるということです。</p><p>もちろんネスティングも可能です。</p> <pre class="code" data-lang="" data-unlink>&gt; (match (T R E &#39;(1) (T B (T R E &#39;(2) E) &#39;(3) E)) ((T R _ x (T B (T R _ y _) z _)) (append x y z))) (1 2 3)</pre><p>この<a class="keyword" href="http://d.hatena.ne.jp/keyword/Haskell">Haskell</a>風の構文糖衣が無ければいかに煩雑なコードになるか、想像に難くないでしょう。</p> </div> <div class="section"> <h4>キーの比較</h4> <p>ツリーのバランスを保ったり、要素を検索したりする上で必要なのが、キーの比較関数です。</p><p>数値や文字列、シンボルなど、比較可能なものであれば何でもキーにできるようにしたいんですが、比較方法を利用側で指定するのはやや面倒なんですね。</p><p>そこで、<a href="http://d.hatena.ne.jp/reinyannyan/20090726/p1">&#x578B;&#x5909;&#x63DB;&#x30E9;&#x30A4;&#x30D6;&#x30E9;&#x30EA;</a>と同じような要領で、予め既知のデータ型については比較関数を用意しておいて、キーが与えられた時にそれに合ったものを選ぶ、という方式を考えました。</p> <pre class="code" data-lang="" data-unlink>(fun (hany f h) (prompt (hash-for-each h (fn (k v) (aif (f k v) (control _ it)))) #f)) (fun (cmp = &lt; x y) (if (= x y) &#39;= (&lt; x y) &#39;&lt; &#39;&gt;)) (define-values (install-cmp comparator) (let ((h (make-hasheq))) (values (fn (? = &lt;) (hash-set! h ? (cmp = &lt;))) (fn (x) (hany (fn (? cmp) (and (? x) cmp)) h)))))</pre><p>データ型に応じた比較関数を以下のようにインストールしておきます。</p> <pre class="code" data-lang="" data-unlink>(install-cmp number? = &lt;) (install-cmp symbol? eq? (fn (x y) (string&lt;? (symbol-&gt;string x) (symbol-&gt;string y)))) ...</pre><p>テスト</p> <pre class="code" data-lang="" data-unlink>&gt; (let ((cmp ((comparator 0) 0))) (values (cmp 1) (cmp 0) (cmp -1))) &lt; = &gt;</pre><p>これにより、ユーザーに対して具体的な比較方法を意識させない仕方でツリー生成関数を提供できることになります。すなわち、比較関数ではなく代表的なキーの値を与えさせるわけです。</p> <pre class="code" data-lang="" data-unlink>(fun (make-tree~ k) (cons (comparator k) empty))</pre><p>(make-tree は構造体の生成関数なので、ライブラリ中では<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C1%A5%EB%A5%C0">チルダ</a>を付けておいて、エクスポート時に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C1%A5%EB%A5%C0">チルダ</a>を外すようにします)</p><p>ツリーの利用者はこのインターフェース関数を呼び出して空のツリーと比較関数を (無意識に) 作ります。</p><p>なおライブラリ側では、全てのツリー操作を通じて最初に作られた比較関数を使い回すようにすべきでしょう。</p> <pre class="code" data-lang="" data-unlink>&gt; (let* ((l &#39;(4 1 2 5 3)) (t (make-tree (car l)))) (eq? (car (foldl (lambda (x t) (tree-set t x &#39;())) t l)) (car t))) #t</pre> </div> <div class="section"> <h4>ツリー更新</h4> <p>ツリーに新しいノードを追加するための基本関数を、このように定義してみました。</p> <pre class="code" data-lang="" data-unlink>(fun (tree-set~ reduce (cons cmp t) k v) (cons cmp (blacken (let loop (((and t (T C a (and x (cons k~ v~)) b)) t)) (if (empty? t) (T R E (cons k v) E) (case (cmp k k~) (&lt; (if (black? t) (balance (loop a) x b) (T R (loop a) x b))) (= (T C a (cons k (reduce k v~ v)) b)) (&gt; (if (black? t) (balance a x (loop b)) (T R a x (loop b))))))))))</pre><p>既に同じキーの要素が存在した場合の対応をパラメータ化することで (reduce)、以下のように自在に派生関数を作り出すことができます。</p><p>上書き版</p> <pre class="code" data-lang="" data-unlink>(define tree-set (tree-set~ (fn (k old new) new)))</pre><p>更新版</p> <pre class="code" data-lang="" data-unlink>(fun (tree-add + t k v) (tree-set~ (fn (k old new) (+ old new)) t k v))</pre><p>追加版</p> <pre class="code" data-lang="" data-unlink>(fun (tree-cons t k v) (tree-add append t k (list v)))</pre><p>なお balance 関数は、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Haskell">Haskell</a> 版そのまんまです。</p> <pre class="code" data-lang="" data-unlink>(define-syntax defmatch (syntax-rules () ((defmatch name ((pat ...) . body) ...) (define name (match-lambda** ((pat ...) . body) ...))))) (defmatch balance (((T R a x b) y (T R c z d)) (T R (T B a x b) y (T B c z d))) (((T R (T R a x b) y c) z d) (T R (T B a x b) y (T B c z d))) (((T R a x (T R b y c)) z d) (T R (T B a x b) y (T B c z d))) ((a x (T R b y (T R c z d))) (T R (T B a x b) y (T B c z d))) ((a x (T R (T R b y c) z d)) (T R (T B a x b) y (T B c z d))) ((a x b) (T B a x b)))</pre> </div> <div class="section"> <h4>応用例 (<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%C7%B0%F8%BF%F4%CA%AC%B2%F2">素因数分解</a>)</h4> <p>tree-add を利用する例として、同じオブジェクトの出現回数を数える、というケースが考えられます。ここでは整数を素因数のリストに変換して、それをさらに<a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a>の数式に変換する、という問題を解きます。</p> <pre class="code" data-lang="" data-unlink>(fun (factorize~ n) (let loop ((n n) (d 2) (l &#39;())) (if (&lt; (/ n d) d) (cons n l) (receive (q r) (quotient/remainder n d) (if (zero? r) (loop q d (cons d l)) (loop n (+ (if (= d 2) 1 2) d) l))))))</pre><p>中身はあまり重要ではないので結果だけ見てください。</p> <pre class="code" data-lang="" data-unlink>&gt; (factorize~ 1984) (31 2 2 2 2 2 2)</pre><p>これにシンボル * を cons すればそのまま掛け算の式になりますが、そうではなく、同じ数の掛け算は指数の形に変換したいです。</p><p>こんな時、tree-add が使えます。</p> <pre class="code" data-lang="" data-unlink>(fun (factorize n) (let ((l (map (fn ((cons n p)) (if (= p 1) n `(expt ,n ,p))) (tree-&gt;alist (foldl (fn (x t) (tree-add + (or t (make-tree x)) x 1)) #f (factorize~ n)))))) (if (null? (cdr l)) (car l) ; prime number (cons &#39;* l))))</pre><p>いちおう検算</p> <pre class="code" data-lang="" data-unlink>&gt; (apply values (map ((fn (x f) (f x)) 1984) (list factorize~ factorize (compose eval factorize)))) (31 2 2 2 2 2 2) (* (expt 2 6) 31) 1984</pre><p>2番目の値がツリーの活用の成果です。細かいですが、2の指数の項が先に来ているのは偶然ではなく、キーの並び順通りにリスト化された結果です。</p><p>なお、ツリーからリストへの変換は以下のように定義しています。</p> <pre class="code" data-lang="" data-unlink>(fun (tree-fold (cons _ t) f seed) (let loop (((and t (T _ a (cons k v) b)) t) (seed seed)) (if (empty? t) seed (loop a (f k v (loop b seed)))))) (fun (tree-map f t) (tree-fold t (fn (k v l) (cons (f k v) l)) &#39;())) (define tree-keys (tree-map (fn (k _) k))) (define tree-values (tree-map (fn (_ v) v))) (define tree-&gt;alist (tree-map cons))</pre> </div> <div class="section"> <h4>既存ノードの更新</h4> <p>キーが既に存在する場合のみ値を更新したい、という要求が生じることもあります。</p><p>これを単純に、ツリーを検索して見つかれば tree-set で更新、というやり方にしてしまうと、同じ検索を2回繰り返すという無駄が生じます。</p><p>また、見つからなければ何もしない、というのは tree-set~ では表現できないことなので、別な関数が必要になります。</p> <pre class="code" data-lang="" data-unlink>(fun (tree-reset new (cons cmp t) k) (cons cmp (or (let loop (((and t (T C a (and x (cons k~ v)) b)) t)) (if (empty? t) #f (case (cmp k k~) (&lt; (aif (loop a) (T C it x b))) (= (T C a (cons k (new v)) b)) (&gt; (aif (loop b) (T C a x it)))))) t)))</pre><p>ツリーが空になった (キーが見つからなかった) 場合に、#f の代わりに空ツリーを返すようにすれば or や aif は不要になるんですが、不要なツリー生成をしないためにこのように工夫してあります。</p><br /> <br /> <p>マクロ: <a href="http://eririn.no.land.to/scheme/anaphora.ss">anaphora.ss</a> <a href="http://eririn.no.land.to/scheme/arcfun.ss">arcfun.ss</a> <a href="http://eririn.no.land.to/scheme/arcif.ss">arcif.ss</a> <a href="http://eririn.no.land.to/scheme/letfun.ss">letfun.ss</a> <a href="http://eririn.no.land.to/scheme/mlfun.ss">mlfun.ss</a></p> </div> Thu, 03 Dec 2009 00:00:00 +0900 hatenablog://entry/17680117127059122723 Scheme 連想リストを適用可能にするために必要なこと https://reinyannyan.hatenadiary.org/entry/20091116/p1 <p>連想リストから値を検索する際に、このように lookup 関数を毎度毎度呼び出すのが面倒だなーと思うことがあります。</p> <pre class="code" data-lang="" data-unlink>(let ((al &#39;((a . 1) (b . 2) (c . 3)))) (list (lookup &#39;a al) (lookup &#39;b al) (lookup &#39;c al)))</pre><p>これを、</p> <pre class="code" data-lang="" data-unlink>(al &#39;a)</pre><p>のように書けたら楽ですよね。(特に、データの提供者と利用者とを分離して、データを集めて提供する側でこのように適用可能な形にして返してくれるのが理想的だと思います。)</p><p>ここで、キーとオブジェクトの順序が lookup の呼び出しとは逆になっていることに着目すると、</p> <pre class="code" data-lang="" data-unlink>(lambda (al) (lambda (k) (lookup k al)))</pre><p>lookup の上に、引数を逆順に受け取るようにして lambda を被せてやれば良いことが分かります。</p><p>この操作を一般化したものを <a class="keyword" href="http://d.hatena.ne.jp/keyword/Haskell">Haskell</a> では flip と呼びます。</p><p>定義</p> <pre class="code" data-lang="" data-unlink>(fun (flip f x y) (f y x))</pre><p>実行例</p> <pre class="code" data-lang="" data-unlink>&gt; (let ((obj (flip lookup &#39;((a . 1) (b . 2) (c . 3))))) (list (obj &#39;a) (obj &#39;b) (obj &#39;c))) (1 2 3)</pre><p>他のものでも試してみましょう。</p><p>例えばリストですが、list-ref は語順が lookup と逆なので、flip で適用可能にすることはできません。</p><p>でも、flip の2乗ならば?</p> <pre class="code" data-lang="" data-unlink>&gt; (define flip^2 (compose flip flip)) &gt; (let ((obj ((flip^2 list-ref) &#39;(a b c)))) (list (obj 0) (obj 1) (obj 2))) (a b c)</pre><p>できました。<a class="keyword" href="http://d.hatena.ne.jp/keyword/vector">vector</a>-ref や string-ref 等でも同様です。</p><p><a href="http://eririn.no.land.to/scheme/mlfun.ss">fun &#x30DE;&#x30AF;&#x30ED;</a>によって flip が (ひいては f が) 部分適用可能になることで、適用可能なオブジェクトが自動的に作られている、というのが今回の要点です。</p><p>いずれにせよ2引数関数にしか使えない技ですが。</p> Mon, 16 Nov 2009 00:00:00 +0900 hatenablog://entry/17680117127059122975 Scheme ifのセマンティクスをarc風にする試み https://reinyannyan.hatenadiary.org/entry/20090928/p1 <p><a href="http://d.hatena.ne.jp/reinyannyan/20090513/p1">anaphoric if</a>を使っていて時々感じるんですが、やはり空リストが偽として扱われない <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> の意味論は、実用上どうしても不便なことが多いです。</p><p>例えば、cdr が空でなければループを続ける</p> <pre class="code" data-lang="" data-unlink>(if (pair? (cdr l)) (loop (cdr l)) ...)</pre><p>というパターンを</p> <pre class="code" data-lang="" data-unlink>(aif (cdr l) (loop it) ...)</pre><p>と書けないようでは、aif の魅力も半減と言わざるを得ません。</p><p>そこで、if 及び関連の構文を arc 風のものに大胆に置き換えることで、上記のようなプログラミングができるようにしようと考えました。</p><br /> <p>まず、どの値を偽とするかを決めます。</p> <pre class="code" data-lang="" data-unlink>(define (ar-false? x) (or (not x) (null? x)))</pre><p>シンボルの <a class="keyword" href="http://d.hatena.ne.jp/keyword/nil">nil</a> は、もともと使ってないので偽としては扱わないことにします。代わりに、</p> <pre class="code" data-lang="" data-unlink>(define-values (t nil) (values #t #f))</pre><p>として <a class="keyword" href="http://d.hatena.ne.jp/keyword/nil">nil</a> の意味をブール値の偽と定めます。</p><p>空リストとしては使えませんが、元来の if に基づいて書かれた関数群 (filter とか) とも共存する必要があるため、この決定はやむを得ません。</p><p>以上に基づいて、arc 風 if はこのように<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BA%C6%B5%A2">再帰</a>的に定義することができます。</p> <pre class="code" data-lang="" data-unlink>(defarc-syntax ar-if (syntax-rules () ((ar-if) nil) ((ar-if expr) expr) ((ar-if test then . else) (if (ar-false? test) (ar-if . else) then))))</pre><p>(defarc-syntax は ar-if を if にリネームしてエクスポートするマクロです)</p><p>MzScheme の if はelse部を省略できない3引数の構文ですが、これはゼロ引数以上のいわゆる variadic な構文です。ちなみにこれのおかげで arc には cond がありません。</p><p>その他</p> <pre class="code" data-lang="" data-unlink>(defarc-syntax ar-and (syntax-rules () ((ar-and) t) ((ar-and expr) expr) ((ar-and expr . rest) (ar-if expr (ar-and . rest))))) (defarc-syntax ar-or (syntax-rules () ((ar-or) nil) ((ar-or expr) expr) ((ar-or expr . rest) (let ((val expr)) (ar-if val val (ar-or . rest)))))) (defarc-syntax ar-when (syntax-rules () ((ar-when test . body) (ar-if test (begin . body))))) (defarc-syntax ar-unless (syntax-rules () ((ar-unless test . body) (ar-if test nil (begin . body)))))</pre><p><br /> 評価例:</p> <pre class="code" data-lang="" data-unlink>&gt; (require &#34;arcif.ss&#34;) &gt; (if) #f &gt; (if (values 1 2)) 1 2 &gt; (if nil (/ 1 0) (cdr &#39;(x)) ((lambda (x) (x x)) (lambda (x) (x x))) t) #t</pre><p><br /> 自分のモジュールに恐るおそるこれらの構文を導入してみて、エラーが出まくるかなと心配したんですが、案外すんなりと新しい (いや、古い、ですね) <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D1%A5%E9%A5%C0%A5%A4%A5%E0">パラダイム</a>に移行できたように思います。もう元の世界には戻れないかも知れません。</p><br /> <p>追記:</p><p>if のインデント用の<a class="keyword" href="http://d.hatena.ne.jp/keyword/elisp">elisp</a>を書いてみました。引数が2の場合は when と同様に、4引数以上の場合は cond っぽくインデントします。</p> <pre class="code" data-lang="" data-unlink>(defun arc-indent-if (state indent-point normal-indent) (let* ((containing-form-start (elt state 1)) (count+pos (count-sexp-args containing-form-start indent-point))) (when count+pos (let ((count (car count+pos)) (pos (cdr count+pos))) (cond ((= count 2) (goto-char containing-form-start) (+ (current-column) lisp-body-indent)) ((= count 3) normal-indent) ((&gt; count 3) (setq normal-indent (progn (goto-char containing-form-start) (forward-char 1) (forward-sexp 1) (1+ (current-column)))) (list (if (evenp pos) (1+ normal-indent) normal-indent) containing-form-start))))))) (defun count-sexp-args (containing-form-start indent-point) (goto-char containing-form-start) (let ((end (condition-case () (save-excursion (forward-sexp 1) (backward-char 1) (skip-chars-backward &#34; \t&#34;) (point)) (error nil))) (count 0) (pos nil)) (when end (condition-case () (progn (forward-char 1) (forward-sexp 1) (while (&lt; (point) end) (forward-sexp 1) (setq count (1+ count)) (unless (or pos (&lt; (point) indent-point)) (setq pos count))) (cons count pos)) (error nil))))) (mapc (lambda (symbol) (put symbol &#39;scheme-indent-function &#39;arc-indent-if)) &#39;(if aif))</pre><p><br /> ファイル:<br /> <a href="http://eririn.no.land.to/scheme/arcif.ss">arcif.ss</a> <a href="http://eririn.no.land.to/scheme/arcfun.ss">arcfun.ss</a> <a href="http://eririn.no.land.to/scheme/mlfun.ss">mlfun.ss</a> <a href="http://eririn.no.land.to/scheme/anaphora.ss">anaphora.ss</a></p> Mon, 28 Sep 2009 00:00:00 +0900 hatenablog://entry/17680117127059123164 Scheme Threaded OR (in PLT Scheme) https://reinyannyan.hatenadiary.org/entry/20090916/p2 <p>ある必要があって、複数のリソースから最初に返答の得られたものを値として採用する、という構文を作ってみました。</p><p>実装:</p> <pre class="code" data-lang="" data-unlink>(define-syntax spawn (syntax-rules () ((spawn . e) (thread (lambda () . e))))) ;; cf: http://scheme.com/tspl4/examples.html#./examples:h11 (define (any-true thunks) (let ((cust (make-custodian))) (let loop ((engs (parameterize ((current-custodian cust)) (map (lambda (t) (let ((c (make-channel))) (spawn (channel-put c (t))) (handle-evt c (lambda (v) (or v c))))) thunks)))) (and (pair? engs) (let ((v (apply sync engs))) (if (channel? v) (loop (remove v engs)) (begin (custodian-shutdown-all cust) v))))))) (define-syntax por ;paralell or (syntax-rules () ((por e ...) (any-true (list (lambda () e) ...)))))</pre><p>書いている時は意識しなかったんですが、TSPL の「エンジン」の例で出てくる por という構文とそっくりだったので、そのようにリネームしました。</p> <pre class="code" data-lang="" data-unlink>(sync evt ...)</pre><p>で最も反応の早かったイベント (の値) が得られるんですが、それが #f だった場合は他のイベントを待つ必要があります。そのために handle-evt という関数で、値が #f の時は値でなくイベント自体を返すように工夫をしています。</p><p>例:</p> <pre class="code" data-lang="" data-unlink>;; Don&#39;t try this at home ;-) &gt; (por ((lambda (x) (x x)) (lambda (x) (x x))) (begin (sleep (expt 2 32)) 1)) 1</pre> Wed, 16 Sep 2009 00:00:01 +0900 hatenablog://entry/17680117127059123555 Scheme flet & labels in Scheme https://reinyannyan.hatenadiary.org/entry/20090916/p1 <p>ループ処理を書く時などに、全く同じ関数呼び出しを複数回書くことがあると思うんですが、それがどうも面倒<a href="#f-a7e0bd06" name="fn-a7e0bd06" title="インターナルdefineを使う方も多いと思うんですが、それだとdefineを何度も書くのが面倒、letだとlambdaを書くのが面倒、ということです">*1</a>なので、ローカル関数を手軽に作る構文を書いてみました。</p><p>マクロ:</p> <pre class="code" data-lang="" data-unlink>(require &#34;mlfun.ss&#34;) (define-syntax define-flet (syntax-rules () ((define-flet flet let) (define-syntax flet (syntax-rules () ((flet ((name params . expr) (... ...)) . body) (let ((name (fn params . expr)) (... ...)) . body))))))) (define-flet flet let) (define-flet flet* let*) (define-flet fletrec letrec) ;`labels&#39; in CL</pre><p>(関数は lambda でも良いんですが、自動カリー化とかパターンマッチを組み込んである自作の fn を使っています)</p><p>例:</p> <pre class="code" data-lang="" data-unlink>(fun (read-header in) (fletrec ((line () (read-line in &#39;any)) (loop (l r) (if (string=? l &#34;&#34;) (reverse r) (loop (line) (cons l r))))) (loop (line) &#39;())))</pre><p><br /> 追記:</p><p>let での変数の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%F3%A5%C7%A5%A3%A5%F3%A5%B0">バインディング</a>と flet のそれとではアリ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C6%A5%A3%A1%BC">ティー</a>が違うことに着目して、変数と関数を両方バインドできるようにしてみました。</p> <pre class="code" data-lang="" data-unlink>(define-syntax define-flet (syntax-rules () ((define-flet flet let) (define-syntax (flet stx) (syntax-case stx () ((flet loop . rest) (and (identifier? #&#39;loop) (eq? (syntax-&gt;datum #&#39;let) &#39;let)) (syntax/loc stx (let loop . rest))) ((flet (bind (... ...)) . body) (quasisyntax/loc stx (let #,(map (lambda (x) (syntax-case x () ((v e) x) ((v p . e) (syntax/loc x (v (fn p . e)))))) (syntax-&gt;list #&#39;(bind (... ...)))) . body))))))))</pre><p>モジュールのエクスポート時に flet を let にリネームすれば、let と flet の構文を統合することが出来ますね。</p><br /> <p>追記2:</p><p>let で関数束縛の構文を使った際に <a class="keyword" href="http://d.hatena.ne.jp/keyword/emacs">emacs</a> でラムダっぽくインデントするようにしてみました。</p> <pre class="code" data-lang="" data-unlink>(defadvice scheme-indent-function (after flet-hack activate) (unless ad-return-value (setq ad-return-value (scheme-indent-flet (ad-get-arg 0) (ad-get-arg 1))))) (defun scheme-indent-flet (indent-point state) (goto-char (elt state 2)) (when (and (eq (char-after) ?\() ;start of params (&lt; (progn (forward-sexp 1) (point)) ;end of params indent-point)) (condition-case () (progn (backward-up-list 3) (forward-char 1) (when (and (looking-at &#34;\\=let&#34;) (&lt; indent-point ;; end of binding (progn (forward-sexp 2) (point)))) (goto-char (elt state 1)) (+ (current-column) lisp-body-indent))) (error nil))))</pre><p>実は cl-indent.el で flet をどうやってインデントしているか調べようとしたんですが、難し過ぎて諦めかけました。</p><br /> <p>ライブラリ:<br /> <a href="http://eririn.no.land.to/scheme/mlfun.ss">mlfun.ss</a></p> <div class="footnote"> <p class="footnote"><a href="#fn-a7e0bd06" name="f-a7e0bd06" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">インターナルdefineを使う方も多いと思うんですが、それだとdefineを何度も書くのが面倒、letだとlambdaを書くのが面倒、ということです</span></p> </div> Wed, 16 Sep 2009 00:00:00 +0900 hatenablog://entry/17680117127059123388 Scheme ルート探索を用いた型変換 https://reinyannyan.hatenadiary.org/entry/20090726/p1 <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> のような、強い型付けがされた言語でプログラミングしていると、型変換の作業が面倒になることがあると思います。あるいは、複数の型のデータが流れ込んできて、それらを統一的に扱いたいというケースもあるかも知れません。</p><p>ということで、型変換の関数を自分で作ることにしたんですが、できるだけ面倒の無いように、変換の方法をプログラムが勝手に調べてくれれば楽だなぁと思いました。</p><p>そこで、次のような方法を考えてみました。</p><p>まず、隣接する型同士の変換関数を洗いざらいリストアップしておきます。型変換のリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トがあると、2つの型を繋ぐルートを調べます。そのルート上にある関数を順に合成することで、離れた型同士の変換関数が自動的に生成される、という要領です。</p><p>以下、定義です。</p> <pre class="code" data-lang="" data-unlink>(fun (hany f h) (reset0 (hash-for-each h (fn (k v) (awhen (f k v) (shift0 _ it)))) #f))</pre><p>ハッシュ表を活用するので、ハッシュ検索の関数を作ってみました。脱出のために<a href="http://d.hatena.ne.jp/reinyannyan/20090623/p1">&#x90E8;&#x5206;&#x7D99;&#x7D9A;</a>を使ってあります。</p> <pre class="code" data-lang="" data-unlink>(define-values (install-type type-of) (let ((h (make-hasheq))) (values (fn (a a?) (hash-set! h a a?)) (fn (x) (hany (fn (a a?) (and (a? x) a)) h)))))</pre><p>型名を登録する関数と、型名を調べる関数です。何となくハッシュ表をグローバルに置きたくなかったのでちょっと妙な定義方法になっています。ローカル変数を複数の関数で共有したい場合の <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> 流のやり方です。</p> <pre class="code" data-lang="" data-unlink>(define-values (relate-types make-search) (let ((h (make-hasheq))) (values (let ((a-hash [hash-ref! h _ (fn () (make-hasheq))])) (fn (a b a-&gt;b) (hash-set! (a-hash a) b a-&gt;b))) (fn (a path) (aand (hash-ref h a #f) (fn (b) (or (hash-ref it b #f) (hany (fn (a~ a-&gt;a~) (aand (not (memq a~ path)) (relation a~ b path) (compose it a-&gt;a~))) it))))))))</pre><p>型同士の関係 (変換関数) を登録する関数と、それをルート探索で調べる関数です。</p><p>探索の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%EB%A5%B4%A5%EA%A5%BA%A5%E0">アルゴリズム</a>は深さ優先と幅優先をミックスしたような感じになっています。が、常に最短経路が見つかるわけではないので、ちょっと回りくどい変換関数が作られてしまうことがあるかも知れません。</p><p>引数の path は、例えば <a class="keyword" href="http://d.hatena.ne.jp/keyword/vector">vector</a> と list の間をぐるぐるループしたりしないように、これまでの経路を覚えておくためのものです。</p> <pre class="code" data-lang="" data-unlink>(fun (relation from to path) (cond ((eq? from to) values) ((make-search from (cons from path)) =&gt; [_ to]) (else #f))) (fun (coerce type x) (or (aand (type-of x) (relation it type &#39;()) (it x)) (error &#39;coerce &#34;coercion method not known for ~s to ~a&#34; x type)))</pre><p>relation は2つの型の関係を返す関数です。これはメモ化した方が良いでしょうね。</p><p>coerce が型変換のインターフェースとなります。type-of でデータの型名を得て、relation でそれと対象の型との関係を調べます。あとはそれをデータに適用すれば良いだけです (<a href="http://d.hatena.ne.jp/reinyannyan/20090513/p1">aand</a> 超便利!)。</p><p>ちなみに、CL や Arc の coerce とは引数の順番が逆になっていますが、自動カリー化が行われる言語ではこういう順番にするのが一般的です。</p><p>関数の定義は以上です。</p><p>型と型変換の定義はこのように分けて行います。</p> <pre class="code" data-lang="" data-unlink>(for-each [apply install-type _] `((char ,char?) (bytes ,bytes?) (number ,number?) (string ,string?) (symbol ,symbol?) (vector ,vector?) (list ,list?))) (for-each [apply relate-types _] `((char string ,string) (char number ,char-&gt;integer) (bytes list ,bytes-&gt;list) (bytes string ,bytes-&gt;string/utf-8) (number string ,number-&gt;string) (string bytes ,string-&gt;bytes/utf-8) (string number ,string-&gt;number) (string symbol ,string-&gt;symbol) (string list ,string-&gt;list) (symbol string ,symbol-&gt;string) (vector list ,vector-&gt;list) (list string ,list-&gt;string) (list vector ,list-&gt;vector)))</pre><p>内容的にはまだまだ不十分ですが、まぁテスト段階なのでこんなものです。本当なら数値型をもっとちゃんと定義したいところです。</p><p>また、バイト列から文字列への変換を、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CA%B8%BB%FA%A5%B3%A1%BC%A5%C9">文字コード</a>を自動検知して変換するようにすれば、例えばポートから文字列まで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CA%B8%BB%FA%A5%B3%A1%BC%A5%C9">文字コード</a>のことを考えることなく変換できるようになるなぁとか、いろいろ想像と期待が膨らみます。</p><p>もちろんユーザー定義の型も登録可能です。が、注意すべきなのは</p> <pre class="code" data-lang="" data-unlink>;; wrong (install-type &#39;atom (negate pair?))</pre><p>のような一般的な型を登録してしまうと、型の検知 (type-of) がうまく機能しなくなるということです。したがって、常に他のデータ型と重ならない、ユニークな型を登録する必要があります。</p><p>ただし、hany の代わりに hmany のような複数の型候補を返す関数を作って、それぞれについて平行して対象との relation を調べるようなシステムに変更すれば、この問題は解決されるんじゃないかと思います。</p><p>あと、リストから文字列への変換は以下のように定義した方が良いかもしれません。</p> <pre class="code" data-lang="" data-unlink>(relate-types &#39;list &#39;string [list-&gt;string (map (coerce &#39;char) _)])</pre><p><br /> 実行例:</p> <pre class="code" data-lang="" data-unlink>&gt; (map (coerce &#39;symbol) (coerce &#39;list #&#34;abc&#34;)) (a b c)</pre><p>バイト列からリストへの変換は一発なんですが、その要素 (整数) からシンボルまでは探索しなければ辿り着けませんね。</p> <pre class="code" data-lang="" data-unlink>&gt; (coerce &#39;symbol (coerce &#39;list #&#34;abc&#34;)) abc</pre><p>これは数値のリストから文字列、さらにシンボルへと変換されています (たぶん)。リストから文字列への変換が list->string のままだとエラーが生じているはずです。<br /> <br /> <br /> </p> <div class="section"> <h4>追記 (探索方式の見直し)</h4> <p>上で、この型システムにはオーバーラップする型を含められない制限があると言ったんですが、それだと一般の数値から整数への変換なども出来ないことに気付きました。ということで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/atom">atom</a> の例えの所で思い付いた hmany 関数というのを作って、複数の型の候補から経路を探索できるようにしてみます。</p> <pre class="code" data-lang="" data-unlink>(fun (hmany f h) (reset0 (hash-for-each h (fn (k v) (awhen (f k v) (shift0 cont (cons it (cont)))))) &#39;()))</pre><pre class="code" data-lang="" data-unlink>(fun (one-or-many l) (cond ((null? l) #f) ((null? (cdr l)) (car l)) (else l))) (define-values (install-type type-of) (let ((h (make-hasheq))) (values (fn (a a?) (hash-set! h a a?)) (fn (x) (aand (one-or-many (hmany (fn (a a?) (and (a? x) a)) h)) (if (pair? it) (sort-types it) it))))))</pre><p>type-of は、このバージョンでは #f か型 (シンボル) か、型のリストを返すようになります。</p> <pre class="code" data-lang="" data-unlink>(define-values (order&lt;types sort-types) (let ((h (make-hasheq))) (values (fn (lo hi) (hash-set! h hi (adjoin lo (hash-ref h hi &#39;())))) (fn (l) (sort l (afn (x y) (aand (hash-ref h y #f) (or (memq x it) (ormap (self x) it)))))))))</pre><p>型のリストを返す場合は、型階層の低い順から並ぶようにします (e.g. (integer number))。経路を全探索した時に、その結果から辺の数が最少のものを正しい経路として採用するんですが、たまに等しい距離の経路が並んでしまうことがあるので、その場合に備えて優先順位を付けるんです。</p><p>型階層は</p> <pre class="code" data-lang="" data-unlink>(define-values (raise-type reduce-type) (values (fn (lo hi raise) (order&lt;types lo hi) (relate-types lo hi raise)) (fn (hi lo reduce) (order&lt;types lo hi) (relate-types hi lo reduce)))) (reduce-type &#39;number &#39;integer (compose inexact-&gt;exact round))</pre><p>のようにして定義します。型システム全体は階層の無いネットワークなんですが、その中に numerical tower のような階層構造を部分グラフとして組み込むというイメージです。</p><p>次に型システムの実装です。</p> <pre class="code" data-lang="" data-unlink>(fun (make-graph) (let* ((h (make-hasheq)) (a-hash [hash-ref! h _ (fn () (make-hasheq))])) (case-lambda ((a) (hash-ref h a #f)) ((a b) (aand (hash-ref h a #f) (hash-ref it b #f))) ((a b a-&gt;b) (hash-set! (a-hash a) b a-&gt;b) a-&gt;b))))</pre><p>初めのバージョンを作った時に、型の集合と型同士の変換の集合がグラフ構造を成すことに気付いたので、このような関数を作ってみました。型システムの定義と、経路探索のメモ化に使用します。</p> <pre class="code" data-lang="" data-unlink>(define-values (relate-types search-paths) (let ((g (make-graph))) (values (fn (a b a-&gt;b) (g a b a-&gt;b)) (afn (a b path) (if (eq? a b) &#39;(()) (aand (g a) (apply append (hmany (fn (a~ a-&gt;a~) (aand (not (memq a~ path)) (self a~ b (cons a path)) (map [append _ `(,a-&gt;a~)] it))) it))))))))</pre><p>search-paths は少々ややこしいですが、型 a から b への経路のリストを返す関数です。self で<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BA%C6%B5%A2%B8%C6%A4%D3%BD%D0%A4%B7">再帰呼び出し</a>することで深さ優先、hmany を使うことで全探索となっています。</p> <pre class="code" data-lang="" data-unlink>(fun (sorted-car &lt; l) (and (pair? l) (car (sort l &lt;)))) (define least-cdr (sorted-car (fn ((cons _ x) (cons _ y)) (&lt; x y)))) (fun (map-types from to) (aand (least-cdr (map [cons _ (length _)] (search-paths from to &#39;()))) (cons (apply compose (car it)) (cdr it)))) </pre><p>map-types は2つの型の間の全ての経路のうち最短のものを選ぶ関数です。</p><p>この経路というのは隣接する型同士の変換関数のリストなので、compose を適用するとそのまま from から to への変換関数になってくれます。</p><p>返り値はその変換関数と経路の長さのペアとなっています。</p><p>メモ化バージョン。</p> <pre class="code" data-lang="" data-unlink>(define-values (map-types remap-types) (let ((g (make-graph))) (values (fn (from to) (or (g from to) (aand (least-cdr (map [cons _ (length _)] (search-paths from to &#39;()))) (g from to (cons (apply compose (car it)) (cdr it)))))) (fn (from to) (set! g (make-graph)) (map-types from to)))))</pre><p>型システムは実行時に自由に変更可能なため、いちおうルートの再計算ができるようにしておきます。</p> <pre class="code" data-lang="" data-unlink>(fun (relation from to) (aand (if (and (symbol? from) (symbol? to)) (map-types from to) (least-cdr (cond ((symbol? from) (filter-map (map-types from) to)) ((symbol? to) (filter-map [map-types _ to] from)) (else (append-map [filter-map (map-types _) to] from))))) (car it))) (fun (coerce type x) (or (aand (type-of x) (relation it type) (it x)) x))</pre><p>relation は map-types の car を取り出す関数となります。</p><p>探索の始点と終点が1対1の場合もあれば、下の例みたいに coerce の第1引数に type-of を使うと多対多となることもあるので、場合分けが必要です。</p><p>1対1以外の場合は、変換関数と辺の数のペアのリストが得られるので、その中から least-cdr で最少辺のものを選んでいます。</p> <pre class="code" data-lang="" data-unlink>(fun (inc n x) (coerce (type-of x) (+ (coerce &#39;integer x) (or n 1))))</pre><p><br /> ライブラリ:<br /> <a href="http://eririn.no.land.to/scheme/mlfun.ss">mlfun.ss</a> <a href="http://eririn.no.land.to/scheme/arcfun.ss">arcfun.ss</a> <a href="http://eririn.no.land.to/scheme/anaphora.ss">anaphora.ss</a></p> </div> Sun, 26 Jul 2009 00:00:00 +0900 hatenablog://entry/17680117127059123712 Scheme ML や Haskell でよく見る ' (プライム) 記号がうらやましかった件について https://reinyannyan.hatenadiary.org/entry/20090624/p1 <p>数学でよく使われる記号だと思うんですが、ML系のプログラムとか論文を読んでいると、1重引用符が識別子の後に付けられているのをよく見かけます。</p><p>ある関数の変種とか、ある変数に基づいて一時変数を作る時などに、いちいち新しい名前を考えなくて済むので便利なんですよね。</p><p>でも <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> や <a class="keyword" href="http://d.hatena.ne.jp/keyword/Lisp">Lisp</a> では、この記号は quote の省略形という特別な意味を持っているので、使いたくても使えません。</p><p>で、何か良い案は無いものかとずっと思っていたんですが、ついに思い付きました。</p><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C1%A5%EB%A5%C0">チルダ</a>です。</p> <pre class="code" data-lang="" data-unlink>(define (map~ f l) (cond ((null? l) l) ((f (car l)) =&gt; (lambda (x) (cons x (map~ f (cdr l))))) (else (let* ((t (cdr l)) (t~ (map~ f t))) (if (eq? t t~) l (cons (car l) t~))))))</pre><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/emacs">emacs</a> の一時ファイルみたいで、まさにぴったりという感じですよね。何で今まで気付かなかったんだろう…</p> Wed, 24 Jun 2009 00:00:00 +0900 hatenablog://entry/17680117127059123942 Scheme 部分継続について本気出して考えてみた https://reinyannyan.hatenadiary.org/entry/20090623/p1 <p>以前何度か部分継続について書いたことがあるんですが、当時は表面的な振る舞いを観察して何となく分かった気になった程度の拙い説明しか出来ませんでした。</p><p>その上、最近のプログラミングでもほとんど活用しておらず、改めて理解し直す必要を感じてきた次第です。</p><p>そこで今回は、部分継続の概念的な理解を目指し、基礎的な事柄を中心にまとめていきたいと思います。</p><p>基本的に PLT <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> (MzScheme) の評価モデルに即して書いていくため、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> 一般に当てはまる話になっていない部分もあるかも知れません。その点ご了承ください。<br /> <br /> </p> <div class="section"> <h4>Redex と継続</h4> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> の評価モデルにおいて、</p> <pre class="code" data-lang="" data-unlink>(+ 1 (+ 2 0))</pre><p>という式を評価するとき、まず</p> <pre class="code" data-lang="" data-unlink>(+ 2 0)</pre><p>の部分が評価され、その結果の値に対して</p> <pre class="code" data-lang="" data-unlink>(+ 1 [])</pre><p>という残りの計算が行われます。</p><p>ここで角括弧で示した部分を reducible expression (redex) と言います。簡約 (単<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BD%E3%B2%BD">純化</a>) 可能な式、という意味です。そして、もうそれ以上簡約できないところまで簡約を続けて値を得ることを「評価」と呼ぶわけです。</p><p>一方、角括弧を包む「残りの計算」の部分を「継続」と言います。</p><p>つまり、redex のある所には常に継続があるのです。両者が対概念と言うか、相補的な関係にあるものだということが分かりますね。</p><p>この redex と継続という、プログラムの評価中に当然に存在して、なおかつ分かちがたく合わさっているもののうち、継続を第一級オブジェクトとして取り出すのが <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> の call/cc 関数です。<br /> <br /> </p> </div> <div class="section"> <h4>継続は関数なり</h4> <p>ここで</p> <pre class="code" data-lang="" data-unlink>(+ 1 [])</pre><p>という継続の意味を考えてみましょう。</p><p>角括弧で示された穴の部分には、何らかの値 (数値) が入ることが期待されます。そこで継続を、この穴の前後の文脈だと考えることができます。</p><p>しかし別の見方をすると、この「文脈」は、値を与えられるとそれに応じて値を返す働きをするわけですから、数学的な意味での関数そのものであるとも言えますね。</p><p>したがって、これは本質的に</p> <pre class="code" data-lang="" data-unlink>(lambda (x) (+ 1 x))</pre><p>という関数に等しいと考えられるわけです。<br /> <br /> </p> </div> <div class="section"> <h4>普通の継続と合成可能な継続</h4> <p>call/cc に触れる前に、MzScheme の プリミティブである call-with-composable-continuation 関数を紹介しておきます。</p> <pre class="code" data-lang="" data-unlink>&gt; (define c #f) &gt; (+ 1 (+ 2 (call-with-composable-continuation (lambda (k) (set! c k) 0)))) 3</pre><p>ここで、call-with-composable-continuation を呼び出した位置がちょうど redex の位置で、それを包む式が継続ということになります。この場合は</p> <pre class="code" data-lang="" data-unlink>(+ 1 (+ 2 []))</pre><p>という文脈が k として返されます (c として保存)。</p><p>これに 0 を与えてみると、当然ながら</p> <pre class="code" data-lang="" data-unlink>&gt; (c 0) 3</pre><p>という値が得られることになります。</p><p>また、c の呼び出しの外側を別の式で包むと</p> <pre class="code" data-lang="" data-unlink>&gt; (+ 1 (c 0)) 4</pre><p>となり、さらに続きの計算が行われています。</p><p>一方、伝統的な call-with-current-continuation (call/cc) はどうでしょうか?</p> <pre class="code" data-lang="" data-unlink>&gt; (+ 1 (+ 2 (call-with-current-continuation (lambda (k) (set! c k) 0)))) 3</pre><pre class="code" data-lang="" data-unlink>&gt; (c 0) 3</pre><p>ここまでは同じです。</p><p>が、次の式は</p> <pre class="code" data-lang="" data-unlink>&gt; (+ 1 (c 0)) 3</pre><p>となります。c を呼び出した時点で脱出が起こるため、この式における継続そのものは破棄されてしまうんです。</p><p>この「脱出」が起こるというのが伝統的な継続の振る舞いであり、脱出を起こさない前者の継続のことは composable continuation と呼びます (他の継続と組み合わせたり、普通の関数と合成することも可能なため)。</p><p>もう一度、その非脱出性を確認しておきましょう。</p> <pre class="code" data-lang="" data-unlink>&gt; (+ 1 (+ 2 (call-with-composable-continuation (lambda (k) (k 0))))) 6</pre><p>となって、</p> <pre class="code" data-lang="" data-unlink>(+ 1 (+ 2 []))</pre><p>という計算が、k が呼ばれた場所とその外側で2回行われていることが分かりますね。<br /> <br /> </p> </div> <div class="section"> <h4>部分継続とは</h4> <p>脱出が起こる起こらないという話をしましたが、では起こる場合、一体どこに向かって脱出するのでしょうか?</p><p>次の例を見てください。</p> <pre class="code" data-lang="" data-unlink>&gt; (+ 1 (+ 2 (call-with-current-continuation (lambda (k) (k 0))))) 3</pre><p>composable の方を使った時は 6 が返ってきた計算ですが、こちらでは k を適用した時点で脱出が起こって、call-with-current-continuation 自体の継続が破棄されているんです。</p><p>で、どこに脱出したかということですが、結論から言うと、トッ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%EC%A5%D9">プレベ</a>ルに向かって脱出しています。</p><p>しかし、これは裏を返せば call/cc がトッ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%EC%A5%D9">プレベ</a>ルからの継続を捉えていたからだと言えるんです。</p><p>実は MzScheme における継続というのは全て部分継続 (delimited continuation) と呼ばれる種類のものです。</p><p>部分継続とは、継続全体のうち、プロンプトという特殊な継続フレーム (後述) で delimit された、つまり、区切られた部分のことです (このことから、プロンプトのことをデリミタと呼ぶ流儀もあります)。</p><p>上の例では REPL 自体が暗黙的にプロンプトに包まれているので、それと call/cc 呼び出しとの間が部分継続として切り取られていたことになります。</p><p>そして、部分継続における脱出というのは対応する (直近の) プロンプトに対して行われるので、この場合は REPL を包んでいるプロンプトに向かって脱出したというわけです。</p><br /> <p>プロンプトというものを理解するために、明示的なプロンプトを導入して実験してみましょう。</p> <pre class="code" data-lang="" data-unlink>&gt; (require scheme/control) &gt; (+ 1 (prompt (+ 2 0))) 3</pre><p>ただ導入しただけでは begin と何の変わりもありません。</p><p>脱出してみます。</p> <pre class="code" data-lang="" data-unlink>&gt; (+ 1 (prompt (+ 2 (abort 0)))) 1</pre><p>トッ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%EC%A5%D9">プレベ</a>ルではなく prompt の位置までの脱出なので、その先の継続はちゃんと評価されていますね。</p><p>当然ながら prompt を外すと</p> <pre class="code" data-lang="" data-unlink>&gt; (+ 1 (+ 2 (abort 0))) 0</pre><p>となります。</p><p>また、</p> <pre class="code" data-lang="" data-unlink>&gt; (define c #f) &gt; (+ 1 (prompt (+ 2 (call-with-current-continuation (lambda (k) (set! c k) 0))))) 3</pre><p>c は prompt までの継続なので、その外側の継続は入っていません。ゆえに、</p> <pre class="code" data-lang="" data-unlink>&gt; (c 0) 2</pre><p>となります。<br /> <br /> </p> </div> <div class="section"> <h4>継続フレーム</h4> <p>MzScheme において継続は、継続フレームという構成単位の連続として実装されています。</p><p>通常の評価過程においても継続フレームは増えたり減ったりしているんですが、これを明示的に操作することで、部分継続やエラー処理などの応用的な制御構造が作られるのです。</p><p>プロンプトというのも継続フレームの一種で、プロンプト・タグという目印によって、部分継続を切り取る際の区切りを示します。</p><p>また、現在の継続を任意のプロンプトに置き換えると、その時の続きの計算は破棄され、制御がプロンプトの位置に移ります。これが脱出のメ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AB%A5%CB">カニ</a>ズムというわけです。<br /> <br /> </p> </div> <div class="section"> <h4>基本的な制御オペレータ - prompt/control, reset/shift</h4> <p>いちいち call-with- なんちゃらとタイプするのは面倒なので、MzScheme ではプロンプトの設定や継続の取り出しのためのオペレータが <a class="keyword" href="http://d.hatena.ne.jp/keyword/scheme">scheme</a>/control ライブラリで提供されています。</p><p>既にその内 prompt と abort を使ったんですが、さらに幾つか例を示していくのでぜひ慣れていってください。</p> <pre class="code" data-lang="" data-unlink>&gt; (+ 1 (prompt (+ 2 (control k 0)))) 1</pre><p>control で部分継続 (k) を取り出している例です。このように prompt で区切られた部分継続は control で捕捉するのが通例となっています。</p><p>control の呼び出しにより、部分継続が取り出されると同時に control 式本体の継続がプロンプトに移ります (つまり、脱出します)。</p><p>ここでは 2 を加える計算を取り出したわけですが、使わずに捨てているので単に abort したのと同じです。</p><p>k を使うと</p> <pre class="code" data-lang="" data-unlink>&gt; (+ 1 (prompt (+ 2 (control k (k 0))))) 3</pre><p>となります。</p><p>繰り返し適用することもできます。</p> <pre class="code" data-lang="" data-unlink>&gt; (+ 1 (prompt (+ 2 (control k (k (k 0)))))) 5</pre><p>これは脱出を伴う call/cc では出来ないんでしたね。</p> <pre class="code" data-lang="" data-unlink>&gt; (+ 1 (prompt (+ 2 (call-with-current-continuation (lambda (k) (k (k 0))))))) 3</pre><p>最初に k を適用したところで脱出が起こるためです。</p><p>control の場合、あくまでも control の呼び出しにおいて脱出が起こるだけで、取り出された k 自体は脱出を起こさない、composable な継続だということが分かります。</p><br /> <p>次に、<a href="http://www.sampou.org/scheme/t-y-scheme/t-y-scheme-Z-H-15.html#node_sec_13.3">&#x72EC;&#x7FD2; Scheme</a> のツリー・マッチングの問題を解いてみましょう。</p><p>prompt/control と似たペアで、reset/shift というのを使います。</p> <pre class="code" data-lang="" data-unlink>(define (tree-&gt;generator t) (reset (let loop ((t t)) (cond ((not (list? t)) (shift k (cons t k))) ((pair? t) (loop (car t)) (loop (cdr t))) (else #f)))))</pre><p>リーフの列挙関数が既に存在した場合は、このように定義することもできます。</p> <pre class="code" data-lang="" data-unlink>(define (tree-&gt;generator t) (reset (for-each-leaf (lambda (x) (shift k (cons x k))) t) ;; Mark the end of traversal #f))</pre><p>すっきりしましたね。いずれにしてもループが回っている途中の状態が k として取り出され、リーフの値と共に外に放出されます。</p><p>shift が一時停止ボタン、k が再生ボタンと考えると分かりやすいかも知れません。取り出されたデータを保存しておいて、(それこそトイレ休憩をはさみつつ) REPL で手作業で回していくことだって可能です。</p><p>ループが回りきると #f が返り、終了が通知されます。</p> <pre class="code" data-lang="" data-unlink>(define (same-fringe? t1 t2) (let loop ((x (tree-&gt;generator t1)) (y (tree-&gt;generator t2))) (or (not (or x y)) (and x y (eqv? (car x) (car y)) (loop ((cdr x) &#39;next) ((cdr y) &#39;next))))))</pre><pre class="code" data-lang="" data-unlink>&gt; (same-fringe? &#39;(1 2 3 4 5) &#39;(1 (((2 ((3)))) (4 (((5))))))) #t</pre><p><br /> ちなみに。tree->generator を再掲しますが</p> <pre class="code" data-lang="" data-unlink>(define (tree-&gt;generator t) (reset (let loop ((t t)) (cond ((not (list? t)) (shift k (cons t k))) ((pair? t) (loop (car t)) (loop (cdr t))) (else #f)))))</pre><p>これは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BF%BC%A4%B5%CD%A5%C0%E8%C3%B5%BA%F7">深さ優先探索</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%EB%A5%B4%A5%EA%A5%BA%A5%E0">アルゴリズム</a>を実装したものです。ところが、これにちょっとした変更を加えるだけで、幅優先バージョンが作れてしまうんです (Biernacki et al.)。</p> <pre class="code" data-lang="" data-unlink>(define (tree-&gt;generator/bf t) (reset (let loop ((t t)) (cond ((not (list? t)) (shift k (cons t k))) ((pair? t) (control k (k #f) (loop (car t)) (loop (cdr t)))) (else #f)))))</pre><p>pair? の節に2行加えただけですよ。</p><p>では確かめてみましょう。</p> <pre class="code" data-lang="" data-unlink>&gt; (define (gen-for-each f g) (when g (f (car g)) (gen-for-each f ((cdr g) #f)))) &gt; (define t &#39;(1 (((2 ((3)))) (4 (((5))))))) &gt; (gen-for-each (lambda (x) (display x) (newline)) (tree-&gt;generator t)) 1 2 3 4 5 &gt; (gen-for-each (lambda (x) (display x) (newline)) (tree-&gt;generator/bf t)) 1 2 4 3 5</pre><p>これはかなり凄いことなんじゃないでしょうか?<br /> <br /> </p> </div> <div class="section"> <h4>control と shift の違い</h4> <p>control で部分継続を捕捉する時は prompt で区切りを設定、shift を使うときは reset で、という風に、決まった組み合わせで使うのが慣例となっているんですが、実は prompt と reset は同じものの別名に過ぎません。</p><p>一方 control と shift は、次のような等式が成り立つ関係にあります。</p> <pre class="code" data-lang="" data-unlink>(shift k k) = (control k (lambda (x) (prompt (k x))))</pre><p>つまり shift は、k が適用される時にその場にプロンプトを設定するという性質があるんです。</p><p>以下の例でその効果の違いを見ることができます。</p> <pre class="code" data-lang="" data-unlink>&gt; (reset (for-each (lambda (x) (shift k (cons x (k &#39;next)))) &#39;(1 2 3)) &#39;()) (1 2 3) &gt; (prompt (for-each (lambda (x) (control k (cons x (k &#39;next)))) &#39;(1 2 3)) &#39;()) (3 2 1)</pre><p>いずれの場合も、k が for-each ループを最後まで回して、その先にある空リストを捉えるという動作をする点では同じです。</p><p>ではなぜ結果が異なるのか、それを理解するために、評価ステップを書き出してみましょう。</p><p>reset/shift を使った方は</p> <pre class="code" data-lang="" data-unlink>(reset (for-each (lambda (x) (shift k (cons x (k &#39;next)))) &#39;(1 2 3)) &#39;()) (reset (cons 1 ((lambda (v) (reset (for-each (lambda (x) (shift k (cons x (k &#39;next)))) &#39;(2 3)) &#39;())) &#39;next))) (reset (cons 1 (reset (for-each (lambda (x) (shift k (cons x (k &#39;next)))) &#39;(2 3)) &#39;()))) (reset (cons 1 (reset (cons 2 ((lambda (v) (reset (for-each (lambda (x) (shift k (cons x (k &#39;next)))) &#39;(3)) &#39;())) &#39;next))))) (reset (cons 1 (reset (cons 2 (reset (for-each (lambda (x) (shift k (cons x (k &#39;next)))) &#39;(3)) &#39;()))))) (reset (cons 1 (reset (cons 2 (reset (cons 3 ((lambda (v) (reset (for-each (lambda (x) (shift k (cons x (k &#39;next)))) &#39;()) &#39;())) &#39;next))))))) (reset (cons 1 (reset (cons 2 (reset (cons 3 (reset (for-each (lambda (x) (shift k (cons x (k &#39;next)))) &#39;()) &#39;()))))))) (reset (cons 1 (reset (cons 2 (reset (cons 3 (reset &#39;())))))))</pre><p>のようになります。</p><p>冒頭の継続と redex の議論でいくと、redex の位置に継続が埋め込まれていくという、逆のことが起こっているようにも見えますね。</p><p>ここで重要なのは、ループごとに次の shift の脱出先が設定されているということです。言い換えると、ネストする shift の呼び出しにおいて、先行する shift で区切られた領域を後続の shift は超えられないようになっているのです。</p><p>この性質が、ローカル変数で言うところのレキシカル (静的) スコープに似ているということから、shift のことを静的な制御オペレータと呼ぶ場合があります。</p><br /> <p>後者では、ループ毎に同じ prompt の位置に脱出しますから cons の呼び出しが常に左側に積み上がっていくことになります。</p> <pre class="code" data-lang="" data-unlink>(prompt (for-each (lambda (x) (control k (cons x (k &#39;next)))) &#39;(1 2 3)) &#39;()) (prompt (cons 1 ((lambda (v) (for-each (lambda (x) (control k (cons x (k &#39;next)))) &#39;(2 3)) &#39;()) &#39;next))) (prompt (cons 1 (let () (for-each (lambda (x) (control k (cons x (k &#39;next)))) &#39;(2 3)) &#39;()))) (prompt (cons 2 ((lambda (v) (cons 1 (let () (for-each (lambda (x) (control k (cons x (k &#39;next)))) &#39;(3)) &#39;()))) &#39;next))) (prompt (cons 2 (cons 1 (let () (for-each (lambda (x) (control k (cons x (k &#39;next)))) &#39;(3)) &#39;())))) (prompt (cons 3 ((lambda (v) (cons 2 (cons 1 (let () (for-each (lambda (x) (control k (cons x (k &#39;next)))) &#39;()) &#39;())))) &#39;next))) (prompt (cons 3 (cons 2 (cons 1 (let () (for-each (lambda (x) (control k (cons x (k &#39;next)))) &#39;()) &#39;()))))) (prompt (cons 3 (cons 2 (cons 1 &#39;()))))</pre><p>やはりここでも shift と同様の議論が成り立って、ネストする control 呼び出しにおいて1つの領域が共有される点が、変数の動的スコープ (dynamic extent) に似ているということで、control のことを動的な制御オペレータと呼びます。<br /> <br /> </p> </div> <div class="section"> <h4>その他のオペレータ</h4> <p>プロンプトを毎回新たに設定する・しないという話をしましたが、実はさらに、直前に設定されたプロンプトを保つ・保たないというバリエーションもあり得るんです。このことから、次の4つのオペレータの組を得ることができます (Shan)。</p> <ul> <li>reset/shift</li> <li>prompt/control</li> <li>reset0/shift0</li> <li>prompt0/control0</li> </ul><p>後の2組がプロンプトを保たないバージョンです。</p><p>例えば前節の例で reset0/shift0 を使うと</p> <pre class="code" data-lang="" data-unlink>(reset0 (for-each (lambda (x) (shift0 k (cons x (k &#39;next)))) &#39;(1 2 3)) &#39;())</pre><p>経過は略しますが</p> <pre class="code" data-lang="" data-unlink>(cons 1 (cons 2 (cons 3 (reset &#39;()))))</pre><p>のように評価され、結果的には同じリストが作られます。ただ、プロンプトが消えてしまうため、shift のような静的な性質は完全には保たれないことになります。</p><p>まぁ、このことに一体どんな便利な応用があり得るのかは、まだ良く分かっていない (研究されていない) んじゃないかと思います。</p><p>ただ、プロンプトを除去することが、保つことに比べて効率的だという実装面での理由があれば、このように結果が変わらない場合には 0 のバージョンを選択できる、という点ではメリットと言えるかも知れません。</p><br /> <p>[追記]</p><p>reset0/shift0 の用例を1つだけ思い付きました。<a href="http://d.hatena.ne.jp/reinyannyan/20080624/p1">Zipper</a> というデータ構造の基本的な<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%C6%A5%EC%A1%BC%A5%B7%A5%E7%A5%F3">イテレーション</a>関数 (fold-zipper) があるんですが、これを reset0/shift0 で定義しておくと</p> <pre class="code" data-lang="" data-unlink>(define (fold-zipper kons knil zipper) (reset0 (traverse-zipper (lambda (zipper) (shift0 k (kons zipper (k #f)))) zipper) knil))</pre><p>それに基づく関数で簡単に値の取り出し (脱出) が出来るようになるんです。</p> <pre class="code" data-lang="" data-unlink>(define (find-zipper p? zipper) (reset0 (fold-zipper (lambda (zipper _) (and (p? (zipper-node zipper)) (shift0 k zipper))) #f zipper)))</pre><p>これは reset/shift を使用した場合は無理です。そう考えると結構便利ですよね。認識不足でした。<br /> <br /> </p> </div> <div class="section"> <h4>参考文献</h4> <p>PLT <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> Reference:<br /> <a href="http://docs.plt-scheme.org/reference/eval-model.html">1.1 Evaluation Model</a><br /> <a href="http://docs.plt-scheme.org/reference/cont.html">9.4 Continuations</a></p><p>Shan, Chung-chieh. <a href="http://repository.readscheme.org/ftp/papers/sw2004/shan.pdf">Shift to Control</a><br /> Biernacki, Dariusz., et al. <a href="http://www.brics.dk/RS/05/13/">On the Dynamic Extent of Delimited Continuations</a></p> </div> Tue, 23 Jun 2009 00:00:00 +0900 hatenablog://entry/17680117127059124117 Scheme もっと! ML/Haskell っぽい関数構文 https://reinyannyan.hatenadiary.org/entry/20090527/p1 <p>ML や <a class="keyword" href="http://d.hatena.ne.jp/keyword/Haskell">Haskell</a> では、関数を定義する時に、変数だけでなく<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%C6%A5%E9%A5%EB">リテラル</a>やデータ・コンスト<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>タなどでパターンマッチをすることができますね。</p><p>PLT <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> にも、それに<a href="http://docs.plt-scheme.org/reference/match.html">&#x3088;&#x304F;&#x4F3C;&#x305F;&#x69CB;&#x6587;</a>があります。</p> <pre class="code" data-lang="" data-unlink>&gt; (require scheme/match) &gt; (match &#39;(1 . 2) ((cons x y) (list x y))) (1 2)</pre><p>これを利用して、以前の <a href="http://d.hatena.ne.jp/reinyannyan/20090127/p1">ML&#x98A8;</a>および <a href="http://d.hatena.ne.jp/reinyannyan/20090424/p1">Haskell&#x98A8;&#x95A2;&#x6570;&#x69CB;&#x6587;</a>を、より本物っぽくする方法を考えてみました。</p><p>結果から言うと、こういう関数定義ができるようになります。</p> <pre class="code" data-lang="" data-unlink>&gt; (fact 0 := 1 n := (* n (fact (- n 1)))) &gt; (fact 5) 120</pre><p>(<a class="keyword" href="http://d.hatena.ne.jp/keyword/Haskell">Haskell</a> の文法とは少し異なる点に注意してください。関数名を繰り返さなくて済むというメリットを重視したものですが、後述のようにデメリットもあります)</p><br /> <p>まず、無名関数をパターンマッチに対応させるところから始めましょう。</p><p>PLT の match 構文では1つのデータに対するパターンマッチしか定義できないので、2変数以上の関数をパターンマッチ対応にするのはちょっと苦労しそうです。</p><p>と思ったんですが、match-let という派生構文と generate-temporaries という関数を使えば簡単でした。</p> <pre class="code" data-lang="" data-unlink>(define-syntax (fn stx) (syntax-case stx () ((fn () . expr) (syntax/loc stx (begin . expr))) ((fn params . expr) (stx-every identifier? #&#39;params) (curry (syntax/loc stx (lambda params . expr)))) ((fn (param ...) . expr) (with-syntax (((g ...) (generate-temporaries #&#39;(param ...)))) (curry (syntax/loc stx (lambda (g ...) (match-let ((param g) ...) . expr))))))))</pre><p>パターンを直接 lambda の変数リストの中に置くことはできないので、一旦パターンに対応する一時変数を用意しておき (generate-temporaries)、それを lambda の変数リストにするんです。</p><p>そして match-let により、元のパターンと受け取った引数の組のリストを平行してマッチしていきます。</p><p>テスト</p> <pre class="code" data-lang="" data-unlink>&gt; ((fn ((cons x y)) (list x y)) &#39;(1 . 2)) (1 2)</pre><p>cons によるパターンマッチを関数のパラメータとしている例です。<a class="keyword" href="http://d.hatena.ne.jp/keyword/Haskell">Haskell</a> だと \(x:y) -> という風に書くところですね。</p><p>もちろん普通の変数をパラメータにすることもできます。</p> <pre class="code" data-lang="" data-unlink>&gt; ((fn ((cons x y) z) (list x y z)) &#39;(1 . 2) 3) (1 2 3) &gt; (((fn ((cons x y) z) (list x y z)) &#39;(1 . 2)) 3) (1 2 3)</pre><p>(カリー化もばっちり!)</p><br /> <p>さて次に関数定義の構文なんですが、これまで導入を見送ってきた<a href="http://www.dcs.ed.ac.uk/home/stg/NOTES/node15.html">&#x8907;&#x6570;&#x306E;&#x7BC0;&#x3067;&#x306E;&#x30D1;&#x30BF;&#x30FC;&#x30F3;&#x30DE;&#x30C3;&#x30C1;</a>をサポートしてみましょう (<a class="keyword" href="http://d.hatena.ne.jp/keyword/Haskell">Haskell</a> 風構文との整合性もあるので fn ではサポートしません)。</p><p>節の区切りには ML と同じく縦棒を使いたいんですが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> (<a class="keyword" href="http://d.hatena.ne.jp/keyword/Lisp">Lisp</a>) ではこれは特別な意味を持つ文字なので、そのまま使うことはできません。</p> <pre class="code" data-lang="" data-unlink>&gt; (string-&gt;symbol &#34;|&#34;) \|</pre><p>というわけで、バックスラッシュで<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ケープする必要がありますね。</p> <pre class="code" data-lang="" data-unlink>(fun (fact 0) 1 \| (fact n) (* n (fact (- n 1))))</pre><p>という定義ができるようにしましょう。</p> <pre class="code" data-lang="" data-unlink>(define-syntax fun (let ((vbar? (same-id? &#39;\|)) (cl:name (lambda (clause) (car (syntax-e (car clause))))) (cl:params (lambda (clause) (cdr (syntax-e (car clause))))) (cl:body cdr)) (lambda (stx) (syntax-case stx () ((fun . clauses) (stx-any vbar? #&#39;clauses) (let ((clauses (stx-break* vbar? #&#39;clauses))) (with-syntax ((name (cl:name (foldl (lambda (x y) (or (and (= (length (cl:params x)) (length (cl:params y))) (free-identifier=? (cl:name x) (cl:name y)) y) (raise-syntax-error #f &#34;malformed definition&#34; stx))) (car clauses) (cdr clauses)))) (temps (generate-temporaries (cl:params (car clauses))))) (quasisyntax/loc stx (define name (fn temps #,(let loop ((cl (car clauses)) (rest (cdr clauses))) (let ((binds (map list (cl:params cl) (stx-&gt;list #&#39;temps))) (body (cl:body cl))) (if (stx-every identifier? (cl:params cl)) ;; No error handlers needed; ignore rest of ;; clauses, if any #`(let #,binds #,@body) #`(with-handlers ((exn:misc:match? (lambda (x) #,(if (pair? rest) (loop (car rest) (cdr rest)) #&#39;(raise x))))) (match-let #,binds #,@body))))))))))) ((fun (name . params) . expr) (syntax/loc stx (define name (fn params . expr))))))))</pre><p>最初の節でマッチに失敗したら例外を捕えて次の節を試す、という流れをお馴染みの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BA%C6%B5%A2">再帰</a>で組み立てています。</p><p>以下は縦棒で区切られた節を集める関数です (stx-break*)。</p> <pre class="code" data-lang="" data-unlink>(require (only-in srfi/1 break)) (define (stx-break p? stx) (receive (l r) (break p? (if (syntax? stx) (syntax-&gt;list stx) stx)) (values l ;; remove separator (if (pair? r) (cdr r) &#39;())))) (define (stx-break* p? stx) (let loop ((l stx) (r &#39;())) (if (stx-null? l) (reverse r) (receive (x y) (stx-break p? l) (loop y (cons x r))))))</pre><p>これで ML 版の関数構文は完成です。</p><p>次に <a class="keyword" href="http://d.hatena.ne.jp/keyword/Haskell">Haskell</a> 版ですが、これは元々 ML 版の糖衣構文だったので楽勝だろうと思いきや、以外とてこずってしまいました。</p><p>無名関数は変更無しなので関連部分のみ示します。</p> <pre class="code" data-lang="" data-unlink>(require (only-in scheme/list add-between)) (define declare? (same-id? &#39;:=)) (define-syntax (app-fun stx) (syntax-case stx () ((app-fun n . e) (stx-any declare? #&#39;e) ;; Convert ;; (fact 0 := 1 ;; n := (* n (fact (- n 1)))) ;; =&gt; ;; (fun (fact 0) 1 ;; \| (fact n) (* n (fact (- n 1)))) (quasisyntax/loc stx (fun #,@(apply append (add-between (map (lambda (x) (list (cons #&#39;n (car x)) (cdr x))) (let loop ((l (syntax-&gt;list #&#39;e)) (r &#39;())) (if (null? l) (reverse r) (receive (v e) (stx-break declare? l) (if (null? e) (raise-syntax-error #f &#34;Malformed definition&#34; stx) (loop (cdr e) (cons (cons v (car e)) r))))))) &#39;(\|)))))) [snip]))</pre><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%C3%E6%C3%D6%B5%AD%CB%A1">中置記法</a>の := では上で作った stx-break* が役に立たないことに気付き、愕然としたのがてこずった理由です。</p><p>また、ML 版と違い、関数のボディーには1つの式しか置けないという制限も生じました (前の実装には無かった制限です)。ボディー部分と次の節の変数リストとの区切りが無いため、やむを得ない措置です。</p><br /> <p>ファイル:<br /> <a href="http://eririn.no.land.to/scheme/mlfun.ss">mlfun.ss</a><br /> <a href="http://eririn.no.land.to/scheme/hasfun.ss">hasfun.ss</a><br /> <a href="http://eririn.no.land.to/scheme/hasfun-helper.ss">hasfun-helper.ss</a><br /> <a href="http://eririn.no.land.to/scheme/arcfun.ss">arcfun.ss</a></p> Wed, 27 May 2009 00:00:00 +0900 hatenablog://entry/17680117127059124443 Scheme Anaphoric If https://reinyannyan.hatenadiary.org/entry/20090513/p1 <p>Arc の aif のように、意図的な変数キャプチャ (マクロ定義の中で作られた変数をマクロユーザーが参照できるようにすること) を用いたマクロを作る時、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> では一般に datum->syntax という関数を使います。</p> <pre class="code" data-lang="" data-unlink>(define-syntax (aif stx) (syntax-case stx () ((aif expr then else) (with-syntax ((it (datum-&gt;syntax #&#39;aif &#39;it))) #&#39;(let ((it expr)) (if it then else))))))</pre><p>datum->syntax の第1引数で、第2引数が有効になる範囲を指定します (ここでは aif 構文内)。なお、この場合 syntax-rules ではなく syntax-case で定義する必要があります。</p><p>これで、</p> <pre class="code" data-lang="" data-unlink>&gt; (aif (assq &#39;a &#39;((a . 1) (b . -2))) (cdr it) 0) 1</pre><p>it によって (assq...) の値が参照できるようになるわけです。</p><p>さて、こちらは PLT <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> のコミュニ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C6%A5%A3%A1%BC">ティー</a>で知られている方法なんですが、</p> <pre class="code" data-lang="" data-unlink>(require scheme/stxparam) (define-syntax-parameter it (lambda (stx) (raise-syntax-error #f &#34;can only be used inside anaphora&#34; stx))) (define-syntax aif (syntax-rules () ((aif expr then else) (let ((val expr)) (if val (syntax-parameterize ((it (make-rename-transformer #&#39;val))) then) else)))))</pre><p>構文パラメータというものによって、syntax-case や datum->syntax を使わずに特別な識別子を導入することができます。</p><p>この aif に基づいて、派生マクロが以下のようにいとも簡単に書けます。</p> <pre class="code" data-lang="" data-unlink>(define-syntax awhen (syntax-rules () ((awhen expr . body) (aif expr (begin . body) (void))))) (define-syntax aand (syntax-rules () ((aand) #t) ((aand expr) expr) ((aand expr . rest) (aif expr (aand . rest) #f)))) (define-syntax acond (syntax-rules (else) ((acond) (void)) ((acond (else . body)) (begin . body)) ((acond (expr) . rest) (or expr (acond . rest))) ((acond (expr . body) . rest) (aif expr (begin . body) (acond . rest)))))</pre><p>構文パラメータを使わないバージョンの aif ではこれは不可能です。上述の datum->syntax の第1引数云々の事情により、it が有効になる場所が元の aif 構文の中だけに限定されてしまうため、派生構文の中では it が使えないのです (実際には不可能ではないらしいんですが、分かりにくい方法なので紹介しないでおきます)。</p><br /> <p>さてここで、前回作った <a href="http://d.hatena.ne.jp/reinyannyan/20090504/p1">Arc &#x306E;&#x89D2;&#x62EC;&#x5F27;&#x69CB;&#x6587;</a>が使えるようにしてみましょう。モジュールを跨いで構文を拡張するのは若干気が引けますが</p> <pre class="code" data-lang="" data-unlink>(require (only-in &#34;arcfun.ss&#34; make-brackets-funny)) (make-brackets-funny aif awhen aand acond)</pre><pre class="code" data-lang="" data-unlink>&gt; (map [aand (cdr _) (abs it)] &#39;((a . 1) (b . -2) (c . #f))) (1 2 #f)</pre><p>あっさり出来ました。</p><br /> <br /> <p>追記:</p><p>モジュールを跨いでマクロを拡張するのは云々という不安の背景について、少し技術的な補足をいたします。</p><p><a href="http://d.hatena.ne.jp/reinyannyan/20090127/p1">&#x30AB;&#x30EA;&#x30FC;&#x5316;&#x95A2;&#x6570;&#x69CB;&#x6587;</a>の部分評価の処理において、local-expand を使って式をフルに展開している箇所があります。</p><p>コードの分析のため、核構文のみの形にする必要があるからなんですが、そうすると、マクロ展開された式の中に、モジュールでエクスポートされていない識別子やローカル変数などが出てくる場合があるんです。</p><p>マクロが定義されているモジュールの外からそのような識別子にアクセスされることは、モジュールの安全性を脅かす事態です。というわけで、MzScheme では <a href="http://docs.plt-scheme.org/reference/stxcerts.html">syntax certificate</a> というものを問題のある識別子に付与し、アクセス権限の無い文脈でのアクセスがあると<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>・エラーを発する仕組みになっています。</p><p>これが実は悩みの種で、以前の実装では、例えば fun 構文の中で aif 構文を使うと</p> <pre class="code" data-lang="" data-unlink>compile: reference is more certified than binding in: val</pre><p>のようなエラーが出てしまっていました。val は anaphora モジュールの中の aif 構文の定義に出てくるローカル変数であるため、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B5%A1%BC%A5%C9%A5%D1%A1%BC%A5%C6%A5%A3%A1%BC">サードパーティー</a>のマクロ (まぁどっちも自分で作ってるんですが) からのアクセスが許可されないわけです。</p><p>悩み抜いた結果、ようやく解決方法が分かったのでここに書いておきます。</p> <pre class="code" data-lang="" data-unlink>(define (collect-certs stx) (let loop ((stx stx) (certs &#39;())) (cond ((syntax? stx) (loop (syntax-e stx) (cons stx certs))) ((pair? stx) (loop (cdr stx) (loop (car stx) certs))) (else certs)))) (define (stx-recertify stx) (let ((certs (collect-certs stx)) (insp (current-code-inspector))) (lambda (stx) (let loop ((certs certs) (stx stx)) (if (null? certs) stx (loop (cdr certs) (syntax-recertify stx (car certs) insp #f))))))) (define (stx-&gt;tree stx stop? (certify values)) (let loop ((stx stx)) (cond ((stop? stx) (certify stx)) ((syntax? stx) (loop (syntax-e stx))) ((pair? stx) (cons (loop (car stx)) (loop (cdr stx)))) (else stx))))</pre><p>collect-certs により、expand して得られた構文オブジェクト (stx) に含まれる全ての syntax certificate を集めます。保護された識別子だけでなく、それを包む式に付与されたものも含めてです。実際に集めているのは構文オブジェクトなんですが、それに certificate が含まれています。</p><p>そして stx-recertify によって、個別に取り出された識別子に対し、集められた certificate を全て与えてしまいます (syntax-recertify)。</p><p>呼び出し方</p> <pre class="code" data-lang="" data-unlink>(stx-&gt;tree expanded-syntax identifier? (stx-recertify expanded-syntax))</pre><p>後はツリーのトラバーサル関数を使って自由にコード変形をすることができます。</p><p>一般に、1つのコードには複数のモジュールに由来するマクロの呼び出しがあるものなので、識別子の出自に関わらず全ての certificate を付与してしまうこの方法は少々乱暴な気もするんですが、エラーが出なくなったので取り敢えず良しとしておきます。</p><br /> <p>ファイル:<br /> <a href="http://eririn.no.land.to/scheme/anaphora.ss">anaphora.ss</a> <a href="http://eririn.no.land.to/scheme/arcfun.ss">arcfun.ss</a> <a href="http://eririn.no.land.to/scheme/hasfun-helper.ss">hasfun-helper.ss</a> <a href="http://eririn.no.land.to/scheme/mlfun.ss">mlfun.ss</a></p><br /> <p>参考文献:<br /> On <a class="keyword" href="http://d.hatena.ne.jp/keyword/Lisp">Lisp</a> - <a href="http://www.bookshelf.jp/texi/onlisp/onlisp_15.html">Anaphoric Macros</a><br /> PLT <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> Blog - <a href="http://blog.plt-scheme.org/2008/02/dirty-looking-hygiene.html">Dirty Looking Hygiene</a></p> Wed, 13 May 2009 00:00:00 +0900 hatenablog://entry/17680117127059124650 Scheme マクロを書くマクロで Arc の角括弧構文 https://reinyannyan.hatenadiary.org/entry/20090504/p1 <p><a href="http://d.hatena.ne.jp/reinyannyan/20090424/p1">Haskell&#x3063;&#x307D;&#x3044;&#x30AB;&#x30EA;&#x30FC;&#x5316;&#x95A2;&#x6570;&#x306E;&#x69CB;&#x6587;</a>で、「<a class="keyword" href="http://d.hatena.ne.jp/keyword/Haskell">Haskell</a>っぽい」と言いながらも Arc の角括弧みたいな構文を導入しました。</p><p>実際にはそれにも偽りがありまして、見た目は Arc っぽいんですが実体は <a href="http://srfi.schemers.org/srfi-26/srfi-26.html">SRFI-26</a> の cut の糖衣構文に過ぎなかったわけです (次のように変換されます)</p> <pre class="code" data-lang="" data-unlink>[+ _ 1] ; =&gt; (cut + &lt;&gt; 1)</pre><p>個人的に cut は見た目が美しくないと感じるので全く使ってなかったんですが、この構文ならスマートですよね。</p><p>ただ、Arc のコードを見ていると、こんな用法もあることに気付きます。</p> <pre class="code" data-lang="" data-unlink>[is (cadr _) name] [if _ (+ _ 1) 1]</pre><p>cut とはまるで別物です。_ で受けた値に関数を適用したり、if 文までも関数化したりしています。</p><p>前者はともかくとして、後者は関数適用の構文をハックする前回の手法ではどうしようもありません。</p><p>便利そうなのでやってみたいんですが、Arc のように reader に手を入れるのは何となく気が引けます (やったことが無いので)。</p><p>あきらめかけたところ、これも何となくなんですが、マクロを1つ書けば良いだけなんじゃ?という閃きがあり、その通りにしてみたら、驚くほど簡単に実現できました。</p><p>先に実装を示します:</p> <pre class="code" data-lang="" data-unlink>(define (syntax-&gt;tree src-stx stop?) (define (cert stx) (syntax-recertify stx src-stx (current-code-inspector) #f)) (let loop ((stx src-stx)) (cond ((stop? stx) (cert stx)) ((syntax? stx) (loop (syntax-e stx))) ((pair? stx) (cons (loop (car stx)) (loop (cdr stx)))) (else stx)))) ;; Adapted from: http://okmij.org/ftp/Scheme/zipper-in-scheme.txt (define (map* f l) (if (not (pair? l)) l (cons (f (car l)) (map* f (cdr l))))) (define (depth-first handle tree) (cond ((handle tree)) ((pair? tree) (map* (lambda (kid) (depth-first handle kid)) tree)) (else tree))) (define (bracketed? stx) (and (syntax? stx) (eq? (syntax-property stx &#39;paren-shape) #\[))) (define (unbracket stx) (syntax-property stx &#39;paren-shape #f)) (define (underscore? stx) (and (syntax? stx) (eq? (syntax-e stx) &#39;_))) (define-syntax (make-brackets-funny stx) (syntax-case stx () ((make-brackets-funny orig) (with-syntax ((new (string-&gt;symbol (format &#34;new-~a&#34; (syntax-e #&#39;orig))))) (syntax/loc stx (begin (define-syntax (new stx) (syntax-case stx () ((new . e) (bracketed? stx) (let ((g (gensym))) (quasisyntax/loc stx (lambda (#,g) (orig . #,(depth-first (lambda (x) (and (underscore? x) g)) (syntax-&gt;tree (unbracket #&#39;e) (lambda (x) (or (identifier? x) (bracketed? x)))))))))) ((new . e) (syntax/loc stx (orig . e))))) (provide (rename-out (new orig))))))) ((make-brackets-funny orig rest ...) (syntax/loc stx (begin (make-brackets-funny orig) (make-brackets-funny rest ...)))))) (make-brackets-funny #%app if or and)</pre><p>関数適用や if 等の構文で角括弧が使われていた場合に、Arc 同様1変数のラムダに変換するという macro-generating macro です (make-<a class="keyword" href="http://d.hatena.ne.jp/keyword/brackets">brackets</a>-funny)。すなわち、例えば</p> <pre class="code" data-lang="" data-unlink>[if _ (+ _ 1) 1] ; =&gt; (lambda (g) (if g (+ g 1) 1))</pre><p>という変換が行われるように任意の構文を拡張するマクロを書くマクロです。</p><p>syntax->tree でコードをコンスのツリー構造に変換、ツリー探索 (depth-first) によりアンダースコアを探して実際の変数 (g) で置き換えていきます。</p><p>ただしその際、角括弧がネストできるように内側の角括弧の _ を置き換えてしまわないよう注意しなければなりません。</p><p>syntax->tree の第2引数はこのための対策で、コード分解を途中で止めるための<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C4%E4%BB%DF%BE%F2%B7%EF">停止条件</a>を与えています。角括弧の式はバラさないでおくことで、内側の _ を置き換えから保護しているわけです。</p><br /> <p>用例を幾つか示しましょう。</p><p>cdr 部が 0 のコンスを探す</p> <pre class="code" data-lang="" data-unlink>&gt; (findf [= (cdr _) 0] &#39;((a . 1) (b . 2) (c . 0))) (c . 0)</pre><p>絶対値関数はこのように書けます。</p> <pre class="code" data-lang="" data-unlink>&gt; (my-abs := [if (negative? _) (- _) _]) &gt; (my-abs 1) 1 &gt; (my-abs -1) 1</pre><p>ネストの例</p> <pre class="code" data-lang="" data-unlink>&gt; ([map [+ _ 1] _] (1 .. 3)) (2 3 4)</pre><p>thunk を呼ぶ例1</p> <pre class="code" data-lang="" data-unlink>&gt; (for-each [newline] (1 .. 3)) &gt;</pre><p>thunk を呼ぶ例2</p> <pre class="code" data-lang="" data-unlink>&gt; (map [_] (list (lambda () 1) (lambda () 2) (lambda () 3))) (1 2 3)</pre><p><br /> ファイル:<br /> <a href="http://eririn.no.land.to/scheme/hasfun.ss">hasfun.ss</a> <a href="http://eririn.no.land.to/scheme/hasfun-helper.ss">hasfun-helper.ss</a> <a href="http://eririn.no.land.to/scheme/mlfun.ss">mlfun.ss</a> <a href="http://eririn.no.land.to/scheme/arcfun.ss">arcfun.ss</a></p> Mon, 04 May 2009 00:00:00 +0900 hatenablog://entry/17680117127059124839 Scheme Haskellっぽいカリー化関数の構文 https://reinyannyan.hatenadiary.org/entry/20090424/p1 <p>以前作った<a href="http://d.hatena.ne.jp/reinyannyan/20090127/p1">ML&#x3063;&#x307D;&#x3044;&#x30AB;&#x30EA;&#x30FC;&#x5316;&#x95A2;&#x6570;&#x306E;&#x69CB;&#x6587;</a>を使って、今度は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Haskell">Haskell</a>っぽいのを作ってみました。</p><p>こういうことが出来るようになります。</p><p>関数定義:</p> <pre class="code" data-lang="" data-unlink>&gt; (add x y := (+ x y)) &gt; (add 1 2) 3 &gt; ((add 1) 2) 3</pre><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%E0%A5%C0%BC%B0">ラムダ式</a>:</p> <pre class="code" data-lang="" data-unlink>&gt; (map (x -&gt; (+ x 1)) &#39;(1 2 3)) (2 3 4) &gt; (map (x y -&gt; (+ x y)) &#39;(1 2 3) &#39;(4 5 6)) (5 7 9)</pre><p>セクション、と言うか Arc の角括弧:</p> <pre class="code" data-lang="" data-unlink>&gt; (map [+ _ 1] &#39;(1 2 3)) (2 3 4) &gt; (map (+ _ 1) &#39;(1 2 3)) (2 3 4)</pre><p><br /> 以下、実装です。関数適用のフォームの評価を<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%BB%A5%D7%A5%C8">インターセプト</a>し、新たな構文規則を追加するという、かなり過激なハックとなっています。</p> <pre class="code" data-lang="" data-unlink>(provide (rename-out (my-app #%app))) (define-syntax (my-app stx) (syntax-case stx () ((my-app n . e) (stx-any declare? #&#39;e) (receive (v e) (stx-break declare? #&#39;e) (quasisyntax/loc stx (fun (n #,@v) #,@e)))) ((my-app . e) (stx-any lambda? #&#39;e) (receive (v e) (stx-break lambda? #&#39;e) (quasisyntax/loc stx (fn #,v #,@e)))) ((my-app . e) (stx-any hole? #&#39;e) (quasisyntax/loc stx (cute #,@(map (lambda (stx) (if (hole? stx) (syntax/loc stx &lt;&gt;) stx)) (syntax-&gt;list #&#39;e))))) ((my-app . e) (syntax/loc stx (#%app . e)))))</pre><p>ユーティリティ:</p> <pre class="code" data-lang="" data-unlink>(begin-for-syntax (fun (same-id? id stx) (eq? id (syntax-e stx))) (define-values (declare? lambda? hole?) (values (same-id? &#39;:=) (same-id? &#39;-&gt;) (same-id? &#39;_))) (define (stx-any p? stx) (ormap p? (syntax-&gt;list stx))) (define (stx-break p? stx) (receive (l r) (break p? (syntax-&gt;list stx)) (values l (cdr r)))))</pre><p>構<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CA%B8%C5%B8">文展</a>開系によって関数適用と見なされた式を捕らえ、特殊な識別子を含むかどうかチェックしています。</p><p>:= を含んでいれば関数定義、-> があれば<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%E0%A5%C0%BC%B0">ラムダ式</a>、という風に分岐して振る舞いを変えるわけです。</p><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Haskell">Haskell</a> みたく = を関数定義のために使いたかったんですが、そうすると関数としての = を引数として渡すことが不可能になるため、:= にしました。</p><br /> <p>追記:</p><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Haskell">Haskell</a>の [i .. j] の構文を加えてみました。</p><p>数値</p> <pre class="code" data-lang="" data-unlink>&gt; (1 .. 10) (1 2 3 4 5 6 7 8 9 10) &gt; (1 3 .. 10) (1 3 5 7 9)</pre><p>文字</p> <pre class="code" data-lang="" data-unlink>&gt; (#\a .. #\c) (#\a #\b #\c) &gt; (#\a #\c .. #\z) (#\a #\c #\e #\g #\i #\k #\m #\o #\q #\s #\u #\w #\y)</pre><p>関数構文と</p> <pre class="code" data-lang="" data-unlink>&gt; (map [+ _ 1] (1 3 .. 10)) (2 4 6 8 10)</pre><p><br /> 実装</p> <pre class="code" data-lang="" data-unlink>(define (stop? start stop) (let ((cmp (if (&gt; stop start) &gt; &lt;))) (lambda (x) (cmp x stop)))) (define (succ start next stop) (cond (next (let ((step (- next start))) (lambda (x) (+ x step)))) ((&gt; stop start) add1) (else sub1))) (define (unfold-args value number start next stop) (receive (start next stop) (values (number start) (and next (number next)) (number stop)) (values (stop? start stop) value (succ start next stop) start))) (define (accum start next stop) (call-with-values (lambda () (cond ((number? start) (unfold-args values values start next stop)) ((char? start) (unfold-args integer-&gt;char char-&gt;integer start next stop)))) unfold)) (define-syntax (app-range stx) (define (stx-every p? l) (andmap p? (map syntax-e l))) (define (literal? . l) (or (stx-every number? l) (stx-every char? l))) (syntax-case* stx (..) (lambda (x y) (eq? (syntax-e x) (syntax-e y))) ((app-range i .. j) (literal? #&#39;i #&#39;j) (quasisyntax/loc stx &#39;#,(accum (syntax-e #&#39;i) #f (syntax-e #&#39;j)))) ((app-range i .. j) (syntax/loc stx (accum i #f j))) ((app-range i j .. k) (literal? #&#39;i #&#39;j #&#39;k) (quasisyntax/loc stx &#39;#,(accum (syntax-e #&#39;i) (syntax-e #&#39;j) (syntax-e #&#39;k)))) ((app-range i j .. k) (syntax/loc stx (accum i j k))) ((app-range . e) (syntax/loc stx (app-fun . e)))))</pre><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%C6%A5%E9%A5%EB">リテラル</a>が与えられた場合、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>時にリスト生成が行われるのがポイントです:</p> <pre class="code" data-lang="" data-unlink>&gt; (syntax-&gt;datum (expand &#39;(1 3 .. 10))) (quote (1 3 5 7 9))</pre><p>iota とかを使うよりも効率が良いわけです。</p><p>変数を含んでいる場合はこのようになります:</p> <pre class="code" data-lang="" data-unlink>&gt; (syntax-&gt;datum (expand &#39;(let ((next 3)) (1 next .. 10)))) (let-values (((next) (quote 3))) (#%app accum (quote 1) next (quote 10)))</pre><p>なお、リスト生成には iota ではなく、同じく <a class="keyword" href="http://d.hatena.ne.jp/keyword/SRFI">SRFI</a>-1 の <a href="http://srfi.schemers.org/srfi-1/srfi-1.html#unfold">unfold</a> という関数を使っています。あまりポピュラーな関数ではないと思うんですが、なかなか奥が深そうです。</p><br /> <p>ファイル:<br /> <a href="http://eririn.no.land.to/scheme/mlfun.ss">mlfun.ss</a><br /> <a href="http://eririn.no.land.to/scheme/hasfun.ss">hasfun.ss</a><br /> <a href="http://eririn.no.land.to/scheme/hasfun-helper.ss">hasfun-helper.ss</a></p><br /> <p>参考文献:<br /> <a href="http://list.cs.brown.edu/pipermail/plt-scheme/2007-June/018694.html">Fun with paran-shape</a></p> Fri, 24 Apr 2009 00:00:00 +0900 hatenablog://entry/17680117127059124979 Scheme Unix シェル的なバックグラウンドでのタスク処理 https://reinyannyan.hatenadiary.org/entry/20090301/p2 <pre class="code" data-lang="" data-unlink>bash&gt; command &amp;</pre><p>みたいに、バックグラウンドで関数を呼び出して、値が必要になった時に表に戻す、というイディオムを思い付きました。</p> <pre class="code" data-lang="" data-unlink>(define (&amp; t) (let ((c (make-channel))) (thread (lambda () (channel-put c (t)))) (lambda (k) (k (channel-get c)))))</pre><p>使い方</p> <pre class="code" data-lang="" data-unlink>(let ((% (&amp; (lambda () some hard work)))) ... (% values))</pre><p>ごちゃごちゃしがちなスレッドの同期処理がスッキリ書けて良いと思います。</p><br /> <p>追記:</p><p>lambda が面倒なので、マクロにしてみました:</p> <pre class="code" data-lang="" data-unlink>(define-syntax &amp; (syntax-rules () ((&amp; e) (let ((c (make-channel))) (thread (lambda () (channel-put c e))) (lambda (k) (k (channel-get c))))) ((&amp; e1 e2 ...) ;; Concurrent version of `begin&#39; (let ((l (list (&amp; e1) (&amp; e2) ...))) (lambda (k) (let loop ((l l)) ((car l) (lambda (v) (if (null? (cdr l)) (k v) (loop (cdr l)))))))))))</pre><p>式が複数有る場合、それぞれをスレッド下で処理するようにしています。begin と同様に、最後の式の値だけ得られるようになってます。</p> Sun, 01 Mar 2009 00:00:01 +0900 hatenablog://entry/17680117127059125301 Scheme ニコ動のコメントをREPLで https://reinyannyan.hatenadiary.org/entry/20090301/p1 <p>最近ニコ動に上がっている作業用BGMとか、音声だけで楽しめるものを<a class="keyword" href="http://d.hatena.ne.jp/keyword/Winamp">Winamp</a>で聴く、ということをしているんですが<br /> <a href="http://www.flickr.com/photos/reinyannyan/3271962536/" title="nico-winamp by reinyanluv, on Flickr"><img src="http://farm4.static.flickr.com/3363/3271962536_a521bdfe73_o.png" width="275" height="116" alt="nico-winamp" /></a><br /> (<a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> でローカルサーバー -> <a class="keyword" href="http://d.hatena.ne.jp/keyword/localhost">localhost</a>:2525/?id=VIDEO_ID 的なURLを<a class="keyword" href="http://d.hatena.ne.jp/keyword/Winamp">Winamp</a>で再生 -> にこさうんど or にこみみのキャッシュを探す -> mp3を<a class="keyword" href="http://d.hatena.ne.jp/keyword/Winamp">Winamp</a>に転送、という流れで。タイトルにダブルクォートが付いているのがウェブ上のリソースという印です。Content-Disposition の filename フィールドを表示しています)</p><p>コメントが見れないのがちょっと寂しくなってきたので、REPLに表示してみました:<br /> <a href="http://www.flickr.com/photos/reinyannyan/3316483950/" title="nicocomment-scheme by reinyanluv, on Flickr"><img src="http://farm4.static.flickr.com/3161/3316483950_cb3dd7d4ee_o.png" width="595" height="597" alt="nicocomment-scheme" /></a><br /> (上のとは別の動画です)</p><p>コメント<a class="keyword" href="http://d.hatena.ne.jp/keyword/XML">XML</a>のタイミング情報の単位がちょっと変わっていて (10ミリ秒) 戸惑いました。あと、<a class="keyword" href="http://d.hatena.ne.jp/keyword/XML">XML</a>の取得に若干時間がかかってしまい、再生から少し送れて表示されてしまうという欠点があるんですが、プレイヤー側で時間調整すれば一応時間通りに表示することができます。</p><br /> <p>追記:</p><p>コメントの出力形式を変えてみました。歌詞職人のコメだけ抽出、みたいなことも出来たり…<br /> <a href="http://www.flickr.com/photos/reinyannyan/3447738688/" title="nico-comment-occur by reinyanluv, on Flickr"><img src="http://farm4.static.flickr.com/3324/3447738688_4fd0e086aa_o.png" width="595" height="597" alt="nico-comment-occur" /></a></p><p>あと、コメント表示が再生開始のタイミングと同期できない問題は、<a href="http://d.hatena.ne.jp/reinyannyan/20090127/p1">&#x30AB;&#x30EA;&#x30FC;&#x5316;&#x95A2;&#x6570;</a>の部分評価の機能により思いがけず解決しました。</p> <pre class="code" data-lang="" data-unlink>(fun (nico-comment-disp ignore? chats) (let ((start (current-milliseconds))) (let loop ((chats chats)) (if (pair? chats) (receive (vpos chat) (values (* (caar chats) 10) (cdar chats)) (cond ((ignore? chat) (loop (cdr chats))) ((&lt;= vpos (- (current-milliseconds) start)) (cout #\; (ms-&gt;time vpos) #\space (sxml:string chat) &#34; \&#34;&#34; (user-id chat) #\&#34; nl) (loop (cdr chats))) (else (sleep 0.333) (loop chats)))) (cout &#34;; end of comments&#34; nl)))))</pre><p>この関数を、先に第1引数に適用してから<a class="keyword" href="http://d.hatena.ne.jp/keyword/XML">XML</a>を取りに行くようにしたところ、startの値がその時点 (再生開始時) でのミリ秒に固定されるので、フルに適用されるまでの時間幅が吸収されるようになったんです。</p><p>まぁ、startの値を関数のパラメータにしておけば良かった話なんですが…</p> Sun, 01 Mar 2009 00:00:00 +0900 hatenablog://entry/17680117127059125119 Scheme Scheme から Emacs のコマンドを実行 https://reinyannyan.hatenadiary.org/entry/20090218/p1 <p>私はよく <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> のプログラムを <a class="keyword" href="http://d.hatena.ne.jp/keyword/Emacs">Emacs</a> 上で実行するんですが、elscreen で裏に回していたりして *<a class="keyword" href="http://d.hatena.ne.jp/keyword/scheme">scheme</a>* バッファが見えない時に、何かユーザーの注意や介入が必要な事態が起こっていることがあります (入力を促す、等)。</p><p>そんな時、外部プログラムを起動するとか、<a class="keyword" href="http://d.hatena.ne.jp/keyword/ffi">ffi</a> で CD トレイを開くとか、その旨を知らせる方法はいくらでも有るわけですが、せっかく <a class="keyword" href="http://d.hatena.ne.jp/keyword/Emacs">Emacs</a> がそこにあるんだから (<a class="keyword" href="http://d.hatena.ne.jp/keyword/beep">beep</a>) の一つも eval 出来ないでどうする、ということで考えてみました。</p><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> と <a class="keyword" href="http://d.hatena.ne.jp/keyword/Emacs">Emacs</a> の間には接続が張られているわけですから、そこにコマンドを流し込み、フック関数でモニターすれば良い、ということで、出来たのがこちら:</p> <pre class="code" data-lang="" data-unlink>(add-hook &#39;inferior-scheme-mode-hook (lambda () (add-hook &#39;comint-preoutput-filter-functions (lambda (s) (if (string-match &#34;^(tell-emacs \\(.*\\))&#34; s) (save-excursion (prin1 (eval (car (read-from-string (match-string 1 s))))) &#34;&gt; &#34;) s)) nil t)))</pre><p>目的のフックがすぐ見つかったので案外易しかったです。"(tell-<a class="keyword" href="http://d.hatena.ne.jp/keyword/emacs">emacs</a> " で始まる行を見つけたらS式を文字列として取り出し、read-from-string で S式化してから eval します。</p><p>さらに、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> 以外でも使えるようにマクロ化してみました (<a class="keyword" href="http://d.hatena.ne.jp/keyword/elisp">elisp</a> には<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%ED%A1%BC%A5%B8%A5%E3">クロージャ</a>が無いので):</p> <pre class="code" data-lang="" data-unlink>(eval-when (load) (defmacro connect-to-emacs (prompt) `(add-hook &#39;comint-preoutput-filter-functions (lambda (s) (if (string-match &#34;^(tell-emacs \\(.*\\))&#34; s) (save-excursion (prin1 (eval (car (read-from-string (match-string 1 s))))) ,prompt) s)) nil t)))</pre><p>プロンプトを言語ごとに設定できるようになっています:</p> <pre class="code" data-lang="" data-unlink>(add-hook &#39;inferior-scheme-mode-hook (lambda () (connect-to-emacs &#34;&gt; &#34;))) (add-hook &#39;shell-mode-hook (lambda () (connect-to-emacs &#34;&#34;)))</pre><p>上記を <a class="keyword" href="http://d.hatena.ne.jp/keyword/.emacs">.emacs</a> に追加しておくと、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> プログラム中で</p> <pre class="code" data-lang="" data-unlink>(display &#39;(tell-emacs (beep)))</pre><p>のように出力するか、あるいはプロンプトで</p> <pre class="code" data-lang="" data-unlink>&gt; &#39;(tell-emacs (beep))</pre><p>と評価することで、任意の <a class="keyword" href="http://d.hatena.ne.jp/keyword/Lisp">Lisp</a> 式を <a class="keyword" href="http://d.hatena.ne.jp/keyword/Emacs">Emacs</a> に送信できるようになります。</p><p>当然ながら</p> <pre class="code" data-lang="" data-unlink>&#39;(tell-emacs (comint-send-string (scheme-proc) &#34;&#39;(tell-emacs (beep))&#34;))</pre><p>てなことも出来ます。</p><p>なお、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Lisp">Lisp</a> 系以外の場合は式を文字列としてプリントする方法を取ってください。シェルモードの例:</p> <pre class="code" data-lang="" data-unlink>echo &#34;(tell-emacs (comint-send-string (scheme-proc) \&#34;&#39;(tell-emacs (beep))\&#34;))&#34; | cat</pre><p>もっと騒がしい通知の例 (<a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> + <a class="keyword" href="http://d.hatena.ne.jp/keyword/Cygwin">Cygwin</a>):</p> <pre class="code" data-lang="" data-unlink>&#39;(tell-emacs (comint-send-string (save-window-excursion (shell)) &#34;cat /dev/urandom &gt; /dev/audio\n&#34;))</pre><p>一見回りくどいですが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Windows">Windows</a> 上の MzScheme からシェルを呼び出すよりちょっと楽だったりします。</p><p>ぜひお試しあれ。</p> Wed, 18 Feb 2009 00:00:00 +0900 hatenablog://entry/17680117127059125433 Emacs Scheme Unix Implicit Function Currying with Automatic Partial Evaluation https://reinyannyan.hatenadiary.org/entry/20090127/p1 <p>関数の部分適用と部分評価を自動的に行うマクロの実装を、日本語と <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> で示していきます。</p> <div class="section"> <h4>動機</h4> <p>前回 <a href="http://d.hatena.ne.jp/reinyannyan/20081229/p1">fun &#x3068; fn</a> という、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Standard%20ML">Standard ML</a> ライクなカリー化関数の構文をご紹介しました。とても便利で既に多用しているんですが、一つどうしても気になるのは、コードが無駄にコピーされてしまう点です。</p><p>例えば</p> <pre class="code" data-lang="" data-unlink>(fn (x y z) (* (+ x y) z))</pre><p>という関数は次のように展開されてしまいます:</p> <pre class="code" data-lang="" data-unlink>(case-lambda ((x) (case-lambda ((y) (lambda (z) (* (+ x y) z))) ((y z) (* (+ x y) z)))) ((x y) (lambda (z) (* (+ x y) z))) ((x y z) (* (+ x y) z)))</pre><p>元の定義の本体部分があちこちに散らばっていますよね。これだとせっかく部分適用ができても、完全に適用されるまで本体の評価が持ち越されることになります。</p><p>この関数の場合、x と y が与えられた時点で (+ x y) は計算可能ですから、その部分は評価しちゃって欲しいところです。このままだと部分適用した関数を map 等で何度も利用する場合にあまり嬉しくありません。</p><p>具体的には、こういう式展開をしてくれれば良いなと思うわけです:</p> <pre class="code" data-lang="" data-unlink>(case-lambda ((x) (case-lambda ((y) (let ((g1 (+ x y))) (lambda (z) (* g1 z)))) ((y z) (* (+ x y) z)))) ((x y) (let ((g2 (+ x y))) (lambda (z) (* g2 z)))) ((x y z) (* (+ x y) z)))</pre> </div> <div class="section"> <h4>方針</h4> <p>まず何をどうするかが問題ですが、基本的には上の展開例のように、与えられた引数のみに依存する式を関数本体から探し出し、評価し、その値で置き換えてやれば良いと思われます。</p><p>ただ、実際には評価を行うのは実行時なので、評価を行うための let 構文を埋め込むというコード変形を行うことになります。</p><p>例えば g1 の部分に注目していただくと、そこのスコープでは x と y が参照可能ですから、本体から x と y の計算式を抜き取り、ラムダの外に追い出して評価しています。これで一度だけの評価で済みますね。</p><p>この一度だけというのがポイントで、もし if 文の中にあってプログラム実行中に一度も評価されない可能性がある式は、部分評価の対象外とすべきでしょう。</p> </div> <div class="section"> <h4>実装</h4> <p>ところで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Lisp">Lisp</a> のプログラムと言えばリスト (ツリー) ですよね。ツリーの探索や置換と言えば、<a href="http://d.hatena.ne.jp/reinyannyan/20080624/p1">zipper</a> を思い出します。ということで、zipper で使ったツリー探索の仕組みをコード変形に適用することにします。</p><p>こういうのです:</p> <pre class="code" data-lang="" data-unlink>;; Adapted from: http://okmij.org/ftp/Scheme/zipper-in-scheme.txt (define (map* f l) (if (not (pair? l)) l (cons (f (car l)) (map* f (cdr l))))) (define (depth-first handle tree) (cond ((not (pair? tree)) tree) ; an atom ((handle tree) =&gt; values) (else ; the node was not handled -- descend (map* (lambda (kid) (depth-first handle kid)) tree))))</pre><p>depth-first を使って式を探索し、置換可能な部分が見つかればその都度変数で置き換えていく、というのが基本戦略となります。</p><p>ただ、関数本体はマクロのパターンマッチで取得するので、初めは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B7%A5%F3%A5%BF%A5%C3%A5%AF%A5%B9">シンタックス</a>・オブジェクトというデータ型になっています。それをツリー構造に展開する関数が必要です:</p> <pre class="code" data-lang="" data-unlink>(define (syntax-&gt;tree x stop?) (cond ((stop? x) x) ((syntax? x) (syntax-&gt;tree (syntax-e x) stop?)) ((pair? x) (cons (syntax-&gt;tree (car x) stop?) (syntax-&gt;tree (cdr x) stop?))) (else x)))</pre><p>使用例:</p> <pre class="code" data-lang="" data-unlink>&gt; (syntax-&gt;tree #&#39;(* (+ x y) 1) identifier?) (#&lt;syntax::229&gt; (#&lt;syntax::232&gt; #&lt;syntax::234&gt; #&lt;syntax::236&gt;) 1)</pre><p>depth-first とは別に、ツリー内部を検索する関数も必要になります:</p> <pre class="code" data-lang="" data-unlink>(define (stx-search p? stx) (cond ((p? stx) #t) ((stx-pair? stx) (or (stx-search p? (stx-car stx)) (stx-search p? (stx-cdr stx)))) (else #f)))</pre><p>(stx-pair? とかは PLT <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> のライブラリ関数です)</p><p>「まだ与えられていない引数」および「関数本体で導入されるレキシカル変数」は参照することが出来ないため、これらに依存する式は評価できません (<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>時に unbound variable エラーになります)。</p><p>なので、置換可能な式を探索する際に、式の中に参照不可能な変数が含まれていないかチェックしなければなりません。</p><p>そしてそのチェックのためには、予めレキシカル変数をコードから抽出しておく必要があります:</p> <pre class="code" data-lang="" data-unlink>(define (match-vars stx) (kernel-syntax-case stx #f ((define-values (v ...) e ...) (syntax-&gt;list #&#39;(v ...))) ((#%plain-lambda (v ...) e ...) (syntax-&gt;list #&#39;(v ...))) ((case-lambda (v e ...) ...) (append-map syntax-e (syntax-&gt;list #&#39;(v ...)))) ((let-values ((v x) ...) e ...) (append-map syntax-e (syntax-&gt;list #&#39;(v ...)))) ((letrec-values ((v x) ...) e ...) (append-map syntax-e (syntax-&gt;list #&#39;(v ...)))) ((set! v e) (list #&#39;v)) (else &#39;()))) (define (collect-vars stx) (cond ((pair? stx) (append (collect-vars (car stx)) (collect-vars (cdr stx)))) ((syntax? stx) (append (match-vars stx) (collect-vars (syntax-e stx)))) (else &#39;())))</pre><p>関数本体のコードに collect-vars を適用すると、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BA%C6%B5%A2">再帰</a>的にレキシカル変数のリストが得られるようになっています。set! で変更される変数もついでに捕捉しておきます。</p><p>メイン部分に入っていきましょう。</p> <pre class="code" data-lang="" data-unlink>(define-syntax (fn stx) (syntax-case stx () ((fn params . exp) (kernel-syntax-case (call-with-values (lambda () (syntax-local-expand-expression #&#39;(lambda params . exp))) (lambda (stx _) stx)) #f ((#%plain-lambda params . exp) (make-cases (cons #&#39;begin (map (lambda (e) ;; Keep protected exp intact (if (protected? e) e (syntax-&gt;tree e identifier?))) (syntax-&gt;list #&#39;exp))) (syntax-&gt;list #&#39;params) (collect-vars #&#39;exp)))))))</pre><p>前のバージョンとの違いの一つは、パターンマッチで得た関数本体 (exp) を local-expand でフル展開しているところです。これは、receive とか and-let* のような派生構文を全て展開してコアの構文のみにするためです。おかげで match-vars の定義が楽になります。</p><p>ただ、これには少し厄介な問題も伴います。フルにマクロ展開をしてしまうと、モジュール内部のエクスポートされていない識別子が展開形の中に出てきてしまう場合があるのです。</p><p>この問題に対し、PLT <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> では、そのような識別子を含む構文オブジェクトに封印のような仕掛けを施しており、少しでも手を加えると封印が破れたことが検知され、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>・エラーが出るようになっています (参照: <a href="http://osdir.com/ml/ballistichelmet.lambda/2004-08/msg00002.html">Fwd: module security</a>)。</p><p>というわけで、封印の掛かっている構文オブジェクトは次の関数でチェックし、部分評価の対象から外さなければいけません。</p> <pre class="code" data-lang="" data-unlink>(define (protected? stx) (stx-search (lambda (x) (and (syntax? x) (syntax-property x &#39;protected))) stx))</pre><p>カリー化定義の本体はこのようになりました:</p> <pre class="code" data-lang="" data-unlink>(define (make-cases exp params locals) (cond ((null? params) exp) ((null? (cdr params)) #`(lambda #,params #,exp)) (else #`(case-lambda #,@(map (lambda (i) (let-values (((bound-params rest-params) (split-at params (+ i 1)))) (if (null? rest-params) #`(#,bound-params #,exp) (let*-values (((residue ev-binds) (peval exp (append rest-params locals))) ((exp2) (make-cases residue rest-params locals))) #`(#,bound-params #,(if (pair? ev-binds) #`(let #,ev-binds #,exp2) exp2)))))) (iota (length params)))))))</pre><p>peval という関数を呼び出している点が前回と異なります。residue と ev-binds という2値を返す関数です。後者はその部分適用の時点で評価可能な式と、それに束縛される変数の組のリストです。前者は部分評価をした後の、小さくなった関数本体を表します。</p><p>peval を<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BA%C6%B5%A2">再帰</a>的に呼び出す度に残りの計算が少なくなっていく、ということが期待できるわけです。</p> <pre class="code" data-lang="" data-unlink>(define (peval exp unbound) (let ((binds &#39;())) (values (depth-first (lambda (e) (cond ((ignore-form? e) e) ;avoid descent ((side-effect? e) #f) ;just descend ((free-from? e unbound) (let ((g (gensym))) (set! binds (cons #`(#,g #,e) binds)) g)) (else #f))) exp) (reverse binds))))</pre><p>ここで depth-first の使い方を説明しておきましょう。</p><p>第1引数の関数で第2引数に含まれるノードを順次受け取っていきます。そして返値として #f を返せばノードに変更を加えず、それ以外の値を返すとノードがその値で置き換えられる仕組みです。</p><p>いずれの場合もそれ以降の探索は続行されるんですが、#f の場合はそのノードの下位ノードへと降りていくのに対し、#f 以外の場合は次のノードに進むという違いがあります。</p><p>peval においては、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B9%BD%CA%B8%CC%DA">構文木</a>の中から簡約可能な式を見つける上で、その下降とかスキップを適宜行っているわけです (if 文は無視して次に進む、等)。</p><p>そして簡約可能な式が見つかると、部分評価のための<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%F3%A5%C7%A5%A3%A5%F3%A5%B0">バインディング</a> #`(#,g #,x) を記録しつつ変数 g で置き換えていきます。</p><p>以下 depth-first 中の条件判断の関数です:</p> <pre class="code" data-lang="" data-unlink>(define (operator exp) (and (pair? exp) (let ((x (car exp))) (and (identifier? x) (pair? (identifier-binding x)) (syntax-e x))))) (define (application? exp) (cond ((operator exp) =&gt; (lambda (op) (and (eq? op &#39;#%app) (let ((proc (cadr exp))) (if (syntax? proc) (syntax-e proc) proc))))) (else #f))) (define (free-from? exp unbound) (and (application? exp) (not (stx-search (lambda (id) (and (identifier? id) (ormap (lambda (ng) (or (bound-identifier=? ng id) (free-identifier=? ng id))) unbound))) exp)))) (define (ignore-form? exp) (let ((forms-to-ignore &#39;(if quote quote-syntax with-continuation-mark #%top #%variable-reference))) (cond ((operator exp) =&gt; (lambda (op) (memq op forms-to-ignore))) (else #f)))) (define (side-effect? exp) (let ((side-effecting-procs &#39;(dynamic-require sleep thread kill-thread call/cc call-with-current-continuation call-with-continuation-prompt ;; and much more ... ))) (stx-search (lambda (e) (cond ((application? e) =&gt; (lambda (op) (memq op side-effecting-procs))) (else #f))) exp)))</pre><p>実装は一応以上です。ソース: <a href="http://eririn.no.land.to/scheme/curry.ss">curry.ss</a><br /> </p> </div> <div class="section"> <h4>利用例</h4> <p>実例を手元で実際に動かしているものから抜粋します:</p> <pre class="code" data-lang="" data-unlink>(fun (nicovideo user watch) (login (lookup &#39;mail user) (lookup &#39;pass user)) (make-immutable-hasheq (let ((vid (video-id watch))) `((video_id . ,vid) ,@(let ((api-url (string-&gt;url (api vid)))) (call/input-url api-url get-pure-port (lambda (in) ;; We need to extend cookie with view history prior to ;; further activities (view-page watch) (form-urlencoded-&gt;alist (port-&gt;string in))) (list (make-cookie-header api-url))))))))</pre><p>これはニコ動<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C0%A5%A6%A5%F3%A5%ED%A1%BC%A5%C0">ダウンローダ</a>の一部で、セッションIDを取得して動画の情報を得る関数です。実際の動画やコメントのダウンロードには別の関数を組み合わせて使います。</p><p>カリー化により第1引数 (ユーザー情報の連想リスト) が先に与えられると、その時点で最初のログインの式が評価されます。したがって、このように</p> <pre class="code" data-lang="" data-unlink>(map (compose nico-download (nicovideo &#39;((mail . &#34;mail@address&#34;) (pass . &#34;passwd&#34;)))) &#39;(&#34;http://www.nicovideo.jp/watch/sm5003587&#34; &#34;http://www.nicovideo.jp/watch/sm2143250&#34; &#34;http://www.nicovideo.jp/watch/sm5008319&#34;))</pre><p>複数の動画を一気にダウンロードする時でも、ログインは1回で済むわけです。</p><p>部分評価が無ければ呼び出しの回数分だけログインしてしまうところですから、大きなメリットと言って良いでしょう。</p> </div> <div class="section"> <h4>問題点</h4> <p>最後に幾つか問題点を挙げておきます。</p><p>第1に、評価順の問題です。</p><p>begin のような構文であっても、式の並び順で評価が行われるとは限りません。簡約可能な式が後ろの方にあるとそれが先に評価されてしまう、という事が起こり得るわけです。</p><p>次に、副作用の問題があります。</p><p>ニコ動の例においては、ログインが1回で済むというのは一見望ましい振る舞いのように思えます。が、一般に副作用を起こす関数が1回の評価で良いのか、何度も評価されて欲しいのか、というのは実は非常に微妙な問題だと思います。</p><p>また、評価順の問題とも絡んで、副作用を伴う式が意図しない順序で評価されると、プログラムが正しく動作しなくなる事もあります。</p><p>第3に、継続や dynamic-wind 等の特殊な制御構造に干渉しないよう注意を払わなければなりません。</p><p>最後に、どこかで変更されるかもしれない<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B0%A5%ED%A1%BC%A5%D0%A5%EB%CA%D1%BF%F4">グローバル変数</a>やパラメータ、あるいはダイナミック変数をキャッシュしてしまう可能性も考慮する必要があるでしょう。</p><br /> <p>なお、これらは全てマクロの実装者側が留意すべき事柄であり、ユーザー側は何も考えなくても勝手に最適化が行われる、というのが理想です。</p> </div> Tue, 27 Jan 2009 00:00:00 +0900 hatenablog://entry/17680117127059125639 Scheme ML っぽいカリー化関数を定義するマクロ https://reinyannyan.hatenadiary.org/entry/20081229/p1 <p>ML とか <a class="keyword" href="http://d.hatena.ne.jp/keyword/Haskell">Haskell</a> のコードを読む時に私がどうしても憧れてしまうのが、自動的にカリー化定義される関数です。</p><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> にもカリー化関数を定義する構文自体は存在します (処理系にもよるでしょうが)。</p><p>例えば、このような<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%E0%A5%C0%BC%B0">ラムダ式</a>のネストで定義された関数を</p> <pre class="code" data-lang="" data-unlink>(define add3 (lambda (a) (lambda (b) (lambda (c) (+ a b c)))))</pre><p>次のようなスタイルで短く書くことができるんです:</p> <pre class="code" data-lang="" data-unlink>(define (((add3 a) b) c) (+ a b c))</pre><p>でも全然自動的ではないですし、必ず定義した通りに適用しなければいけません:</p> <pre class="code" data-lang="" data-unlink>(((add3 1) 2) 3)</pre><p>2番目と3番目の引数を同時に与える、とかは出来ないわけです。</p><p>そこでちょっと知恵をひねりまして、case-lambda を使って、あらゆる関数適用のパターンに応じた<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%E0%A5%C0%BC%B0">ラムダ式</a>をあらかじめ作っておく、という方法を考えてみました:</p> <pre class="code" data-lang="" data-unlink>(define-syntax (fun stx) (define (make-cases vars body) #`(case-lambda #,@(for/list ((i (in-range (length vars)))) (let-values (((hd tl) (split-at vars (+ i 1)))) #`(#,hd #,(if (null? tl) body (make-cases tl body))))))) (syntax-case stx () ((fun (fn v ...) e ...) #`(define fn #,(make-cases (syntax-&gt;list #&#39;(v ...)) #&#39;(begin e ...))))))</pre><p>これを使うと、</p> <pre class="code" data-lang="" data-unlink>(fun (add3 a b c) (+ a b c))</pre><p>という定義は以下のように展開されます:</p> <pre class="code" data-lang="" data-unlink>(define add3 (case-lambda ((a) (case-lambda ((b) (case-lambda ((c) (begin (+ a b c))))) ((b c) (begin (+ a b c))))) ((a b) (case-lambda ((c) (begin (+ a b c))))) ((a b c) (begin (+ a b c)))))</pre><p>コードの重複が多少気になりますが、まぁとりあえず動かしてみましょう。</p> <pre class="code" data-lang="" data-unlink>(((add3 1) 2) 3) ; =&gt; 6</pre><pre class="code" data-lang="" data-unlink>((add3 1) 2 3) ; =&gt; 6</pre><pre class="code" data-lang="" data-unlink>(add3 1 2 3) ; =&gt; 6</pre><p>上手く行ってますね。</p><p>明示的な curry との併用なんかはどうでしょう? MzScheme の <a class="keyword" href="http://d.hatena.ne.jp/keyword/scheme">scheme</a>/function ライブラリで提供されているものを使います:</p> <pre class="code" data-lang="" data-unlink>((curry add3 1) 2 3) ; =&gt; 6</pre><pre class="code" data-lang="" data-unlink>((curry (add3 1) 2) 3) ; =&gt; 6</pre><p>出来ました。</p><p>今度は compose プラス多値と組み合わせてみたり</p> <pre class="code" data-lang="" data-unlink>((compose add3 (lambda (x) (values x 2 3))) 1) ; =&gt; 6</pre><pre class="code" data-lang="" data-unlink>((compose (add3 1) (lambda (x) (values x 3))) 2) ; =&gt; 6</pre><p>もいっちょ</p> <pre class="code" data-lang="" data-unlink>((compose (curry add3 1) values) 2 3) ; =&gt; 6</pre><p>良い感じです。</p><br /> <p>[追記]</p><p>年を跨いで、より完全なバージョンを作ってみました。カリー化された無名関数を作るマクロ fn を加え、ライブラリとしての体裁も整えました:</p> <pre class="code" data-lang="" data-unlink>#lang scheme/base (require (for-syntax scheme/base (only-in srfi/1 iota split-at))) (provide fun fn) (define-for-syntax (make-cases vars body) (cond ((null? vars) body) ((null? (cdr vars)) #`(lambda #,vars #,body)) (else #`(case-lambda #,@(map (lambda (i) (let-values (((hd tl) (split-at vars (+ i 1)))) #`(#,hd #,(make-cases tl body)))) (iota (length vars))))))) (define-syntax (fn stx) (syntax-case stx () ((fn (v ...) e ...) (make-cases (syntax-&gt;list #&#39;(v ...)) #&#39;(begin e ...))))) (define-syntax fun (syntax-rules () ((fun (f v ...) e ...) (define f (fn (v ...) e ...)))))</pre><p>バージョン 4 以降の PLT <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> では、マクロ展開のフェーズは実行時とは別環境になるので注意が必要です。例えばマクロ展開時にライブラリ関数を使いたい場合は (require (for-syntax ... )) として読み込む必要があります。マクロ用に関数を定義したい場合も、define-for-syntax で定義するか、begin-for-syntax で囲んで define するかしなければなりません。</p><p>そういう区別が特に無い処理系では define-for-syntax を普通に define に置き換えれば動くと思います。なお、case-lambda は大抵の処理系でプリミティブもしくは <a class="keyword" href="http://d.hatena.ne.jp/keyword/srfi">srfi</a>-16 で提供されています。</p><p>上で言い忘れたんですが、あくまでも ML スタイル (ラムダ算法においてもそうですが、「全ての関数は1引数関数である」というもの) の模倣ですので、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Lisp">Lisp</a> 的なオプショナル引数やキーワード引数、可変数引数はサポートしません。</p><p>マクロの定義上、零引数の関数も作れるようになっていますが、その場合は</p> <pre class="code" data-lang="" data-unlink>(fn () 1) ; =&gt; 1</pre><p>関数ではなく定数が返るようになっています。これは定数を零引数関数と見なす数学の考え方とも一致します。</p><br /> <p>[追記2]</p><p>fn を用いて、既存の関数をカリー化するマクロを作ってみました:</p> <pre class="code" data-lang="" data-unlink>(define-syntax (make-curried stx) (syntax-case stx () ((make-curried (f n) ...) #`(begin #,@(map (lambda (f n) #`(define #,(datum-&gt;syntax f (string-&gt;symbol (format &#34;~a.&#34; (syntax-e f)))) #,(let ((args (map (lambda (_) (gensym)) (iota (syntax-e n))))) #`(fn #,args (#,f #,@args))))) (syntax-&gt;list #&#39;(f ...)) (syntax-&gt;list #&#39;(n ...)))))))</pre><p>こういう風に、関数名と引数の数のペアを指定する方式です (複数可):</p> <pre class="code" data-lang="" data-unlink>(make-curried (map 2) (+ 2)) (define map-add1 (map. (+. 1))) (map-add1 &#39;(1 2 3)) ; =&gt; (2 3 4)</pre><p>(元の関数と同名で定義したかったんですが、再バインドしようとするとエラーになってしまうのであきらめました)</p><p>元の関数の arity はどうあれ、指定した個数の引数しか受け取れない関数が作られます:</p> <pre class="code" data-lang="" data-unlink>(+. 1 2) ; =&gt; 3 (+. 1 2 3) ; =&gt; procedure +.: no clause matching 3 arguments: 1 2 3</pre><p>case-lambda がエラーを出していますね。</p><p>可変数引数やオプショナル引数等は捨てなければいけませんが、関数によってはかなり便利なんじゃないかと思います。</p> Mon, 29 Dec 2008 00:00:00 +0900 hatenablog://entry/17680117127059125860 Scheme パーサーコンビネータの性能向上について https://reinyannyan.hatenadiary.org/entry/20081212/p1 <p>自前の <a class="keyword" href="http://d.hatena.ne.jp/keyword/XML">XML</a> パーサーやウェブ・<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EC%A5%A4%A5%D4%A5%F3%A5%B0">スクレイピング</a>などに<a href="http://d.hatena.ne.jp/reinyannyan/20080812/p1">&#x30D1;&#x30FC;&#x30B5;&#x30FC;&#x30B3;&#x30F3;&#x30D3;&#x30CD;&#x30FC;&#x30BF;&#x30FB;&#x30E9;&#x30A4;&#x30D6;&#x30E9;&#x30EA;</a>を使っているんですが、どうも実行速度が遅いのが気になってきたので、原因を考えてみました。</p><p>で、気付いたんですが、例えばこのようなパーサーを定義した時に、</p> <pre class="code" data-lang="" data-unlink>(doP (char #\a) (char #\A) (char #\B))</pre><p>パーサーを実行する度に (char #\a) のような関数適用が新たに評価されてしまうことが一つの原因ではないかと思いました。</p><p>そこで、関数適用の形になっているパーサーは1回だけ評価してキャッシュしておくことで、実行効率の向上を図ることにしました。</p><p>(以下 <a class="keyword" href="http://d.hatena.ne.jp/keyword/Gauche">Gauche</a> の PEG ライブラリの実装を参考にさせていただきましたが、少し工夫を加えた部分もあります。)</p><br /> <p>大まかなイメージとしては、上記のパーサーの場合</p> <pre class="code" data-lang="" data-unlink>(let ((tmp1 (char #\a)) (tmp2 (char #\A)) (tmp3 (char #\B))) (lambda (input) ...))</pre><p>のような形に展開し、入力ストリームを受け取るラムダの外側にパーサーをキャッシュしておけば良いわけです (内側だと毎回評価してしまいます)。</p><p>ただし、上と同じ意味のパーサーでも、次のように変数束縛を伴う定義の場合には問題が生じます。</p> <pre class="code" data-lang="" data-unlink>(doP (a &lt;- (char #\a)) (A &lt;- (char (char-upcase a))) (char (integer-&gt;char (+ (char-&gt;integer A) 1))))</pre><p>同じように展開してしまうと</p> <pre class="code" data-lang="" data-unlink>(let ((tmp1 (char #\a)) (tmp2 (char a)) (tmp3 (char (integer-&gt;char (+ (char-&gt;integer A) 1))))) (lambda (input) ...))</pre><p>どこにもバインドされていない変数 (a, A) を参照することになり、エラーになってしまうんです。</p><p>話を具体的にするために、別の例を出しましょう。引用符に囲まれた文字列を取り出すパーサーを考えてみます:</p> <pre class="code" data-lang="" data-unlink>(doP (q &lt;- (one-of (string-&gt;list &#34;\&#34;&#39;&#34;))) (s &lt;- (many-till (none-of (list q)) (char q))) (return (list-&gt;string s)))</pre><p>この場合、1行目のパーサーは意味が変わらないのでキャッシュしたいんですが、2つ目のパーサーは q の値によって意味が変わるので毎回評価しなければいけませんよね。</p><p>このように、キャッシュ出来るパーサーと出来ないパーサーがあるわけです。</p><p>この問題に対処するため、パーサー式を分析して、doP 構文内で束縛された変数を含む場合はキャッシュしないようにする方法を考えてみました。</p><p><a href="http://www.r6rs.org/final/html/r6rs-lib/r6rs-lib-Z-H-13.html#node_sec_12.5">bound-identifier=?</a> という関数を使います。</p> <pre class="code" data-lang="" data-unlink>(define (memvar exp vars) (and (pair? vars) (let bound? ((exp exp)) (if (identifier? exp) (ormap (lambda (var) (bound-identifier=? exp var)) vars) (let ((exps (syntax-e exp))) (and (pair? exps) (ormap bound? exps)))))))</pre><p>syntax-case マクロの中で呼び出す用の関数です。exp はパーサー式、vars は束縛変数のリストで、それぞれ型は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B7%A5%F3%A5%BF%A5%C3%A5%AF%A5%B9">シンタックス</a>・オブジェクト、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B7%A5%F3%A5%BF%A5%C3%A5%AF%A5%B9">シンタックス</a>・オブジェクトのリストです。</p><p>例えば exp として (char a) のような、変数を含む式を受け取った場合、変数 a が vars に含まれていれば #t を返します。</p><p>doP 構文を展開する際に束縛変数をリストに集めていき、パーサー式を見つける度に memvar 関数でそのリストと照合することで、束縛変数を参照しているかどうかが分かる仕組みです。</p><p>パーサー式が既に変数であるか、または束縛変数を参照していればそのままにしておき、それ以外であれば一時変数としてキャッシュする、という方針で doP 構文をこのように定義してみました:</p> <pre class="code" data-lang="" data-unlink>(define-syntax (doP stx) (define (finish-body pre-binds var&amp;parsers) (with-syntax ((parse-it (let loop ((input #&#39;input) (var&amp;parsers var&amp;parsers)) (if (null? (cdr var&amp;parsers)) (syntax-case (car var&amp;parsers) (return) ((return x) #`(values #f x #,input)) (p #`(p #,input))) (with-syntax ((input2 (gensym))) (syntax-case (car var&amp;parsers) (&lt;-) ((v &lt;- p) #`(receive (err v input2) (p #,input) (if err (values err v input2) #,(loop #&#39;input2 (cdr var&amp;parsers))))) (p #`(receive (err v input2) (p #,input) (if err (values err v input2) #,(loop #&#39;input2 (cdr var&amp;parsers))))))))))) #`(let #,pre-binds (lambda (input) parse-it)))) (syntax-case stx () ((doP p ...) (let loop ((pre-binds &#39;()) (var&amp;parsers &#39;()) (bound-vars &#39;()) (clauses (syntax-&gt;list #&#39;(p ...)))) (if (null? clauses) (finish-body pre-binds (reverse var&amp;parsers)) (syntax-case (car clauses) (&lt;- return) ((return x) (loop pre-binds (cons #&#39;(return x) var&amp;parsers) bound-vars (cdr clauses))) ((v &lt;- p) (if (or (identifier? #&#39;p) (memvar #&#39;p bound-vars)) (loop pre-binds (cons #&#39;(v &lt;- p) var&amp;parsers) (cons #&#39;v bound-vars) (cdr clauses)) (with-syntax ((tmp (gensym))) (loop (cons #&#39;(tmp p) pre-binds) (cons #&#39;(v &lt;- tmp) var&amp;parsers) (cons #&#39;v bound-vars) (cdr clauses))))) (p (if (or (identifier? #&#39;p) (memvar #&#39;p bound-vars)) (loop pre-binds (cons #&#39;p var&amp;parsers) bound-vars (cdr clauses)) (with-syntax ((tmp (gensym))) (loop (cons #&#39;(tmp p) pre-binds) (cons #&#39;tmp var&amp;parsers) bound-vars (cdr clauses)))))))))))</pre><p>以前の実装では doP 構文は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E2%A5%CA%A5%C9">モナド</a>の >>= 関数への糖衣構文だったので、パーサーとは独立の純粋に抽象的な定義だったんですが、この変更により doP マクロの中でパーサー連結の詳細を記述しなければいけなくなったのがちょっと残念です。</p><p>同じ事の定義が別々の場所にあるのは不自然なので、>>= の定義を逆に doP 構文に依存させるように変更しました:</p> <pre class="code" data-lang="" data-unlink>(define (&gt;&gt;= p f) (doP (x &lt;- p) (f x)))</pre> Fri, 12 Dec 2008 00:00:00 +0900 hatenablog://entry/17680117127059126014 Scheme パーサーコンビネータで作るインタープリタ https://reinyannyan.hatenadiary.org/entry/20081130/p1 <p>ふと、<a href="http://d.hatena.ne.jp/reinyannyan/20080812/p1">&#x30D1;&#x30FC;&#x30B5;&#x30FC;&#x30B3;&#x30F3;&#x30D3;&#x30CD;&#x30FC;&#x30BF;</a>でパーサーを作ると、それをそのまま<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%D7%A5%EA%A5%BF">インタープリタ</a>として走らせられるんじゃないか、ということを思い付きました。</p><p>普通はファイルやネットワークの入力ポートを</p> <pre class="code" data-lang="" data-unlink>(parse p input-port)</pre><p>のようにして渡すことで (p はパーサー関数)、パースが行われます。</p><p>ここに (current-input-port) を渡してやれば、キー入力を受け取って解析してくれる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%D7%A5%EA%A5%BF">インタープリタ</a>が即出来上がるんじゃないか、という思惑です。</p><p>が、実際やってみたところ、ちょっと上手くいきませんでした。</p><p>parse 関数の中でポートが遅延リスト (ストリーム) 化されるんですが、どうもそこで eof-object を受け取るまで入力を待ち続ける仕様であるために、パースが終了できないみたいです。</p><p>キーボードで eof-object を入力する方法があれば、あるいは、パースに成功したらその時点で入力を待つのを止められれば良いと思うんですが、ちょっとやり方が分かりませんでした。</p><p>ということで、単純に read-line で入力を受け取る方式でやってみました:</p> <pre class="code" data-lang="" data-unlink>(require &#34;parser.ss&#34;) (define (prompt) (printf &#34;~%&gt;&gt;&gt; &#34;) (flush-output (current-output-port)) (read-line)) (define (repl p) (let loop ((input (prompt))) (printf &#34;~s~%&#34; (parse p input)) (loop (prompt))))</pre><p>これに、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C3%E6%C3%D6%B5%AD%CB%A1">中置記法</a>の四則演算をするパーサー (<a href="http://eririn.no.land.to/scheme/arith.ss">arith.ss</a>) を与えてみると:</p> <pre class="code" data-lang="" data-unlink>&gt; (repl (dynamic-require &#34;arith.ss&#34; &#39;arith)) &gt;&gt;&gt; 1 + 2 3 &gt;&gt;&gt; 1 + 2 * 3 7 &gt;&gt;&gt; (1 + 2) * 3 9</pre><p>\(^o^)/</p><p>実にあっけないですが、パーサー自体が元々評価器としての機能を持っていたためにこういうことが可能なわけです。</p><p>ただし read-line を使っているため、1行で完結する式しか受け取れないのが欠点ですね。ちょっと工夫してみました:</p> <pre class="code" data-lang="" data-unlink>(define (append-lines x y) (cond ((not x) y) ((not y) x) (else (string-append x (string #\newline) y)))) (define (read-input) (let loop ((input #f)) (cond ((char-ready?) (loop (append-lines input (read-line)))) ((and input (&gt; (string-length input) 0)) input) (else (sleep 0.5) (read-input)))))</pre><p>read-line の所を read-input に置き換えると、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Emacs">Emacs</a> だと CTRL-j で改行しながら入力できるようになります。</p><br /> <p>環境というものを持たないので本格的な<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0%B8%C0%B8%EC">プログラミング言語</a>は動かせなさそうなのが問題かなと思いますが、解決可能かどうか考え中です。</p> Sun, 30 Nov 2008 00:00:00 +0900 hatenablog://entry/17680117127059126286 Scheme なぜ JavaScripter が Schemer になったか https://reinyannyan.hatenadiary.org/entry/20081115/p1 <p>以前は <a class="keyword" href="http://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a> のことばかり書いていたのが信じられないくらい、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> のことばかり書いていることについての説明文です。</p> <div class="section"> <h4>ホップ</h4> <p>当ダイアリーのタイトルからもお分かりかもしれませんが、私は元々 <a class="keyword" href="http://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a> について書きたくなって、ブログというものを始めました (ついでに言うと、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%CF%A4%C6%A4%CA">はてな</a>のアカウントを取った時点ではそんなつもりは1ミリも有りませんでした)。</p><p>ちょうど世界的に <a class="keyword" href="http://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a> の真価が理解されるようになった時期でもあり、個人的には <a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> のクラス・システムを <a class="keyword" href="http://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a> で模倣するというテーマに興味を持って色々と実験をしたものです。</p><p>それから、<a class="keyword" href="http://d.hatena.ne.jp/keyword/LDR">LDR</a> との出会いという大きな経験もありました。プログラムの世界に深く沈潜するようなリーディング体験をしたのは、この時が初めてだったかもしれません。それまで考えもしなかったような発想や技術に溢れていました。</p><p>さらに、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Mochikit">Mochikit</a> というライブラリにも出会いました。後から考えると、これがその後の方向転換への助走となったように思います。</p> </div> <div class="section"> <h4>ステップ</h4> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Python">Python</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DE">プログラマ</a>によって書かれた <a class="keyword" href="http://d.hatena.ne.jp/keyword/Mochikit">Mochikit</a> は、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Python">Python</a> 風の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B4%D8%BF%F4%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0">関数プログラミング</a>のイディオムを具備しています。ここにおいて初めて、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B4%D8%BF%F4%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0">関数プログラミング</a>の概念にまともに衝突することになったのです。</p><p>思考のメタモルフォーゼとでも言いましょうか。それまで<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AA%A5%D6%A5%B8%A5%A7%A5%AF%A5%C8%BB%D8%B8%FE">オブジェクト指向</a>しか知らなかった人間にとって、使ったことの無い頭の使い方を強いられるという意味で、苦しい経験だったと言えます。</p><p>その経験を経て、私の興味の対象は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B4%D8%BF%F4%B7%BF%B8%C0%B8%EC">関数型言語</a>へと大きくシフトしました。そしてどれか一つ、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B4%D8%BF%F4%B7%BF%B8%C0%B8%EC">関数型言語</a>を選んで勉強しよう、と思ったんです。</p><p>調べた結果、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Lisp">Lisp</a> 系の言語を選ぶことにしました。最初は <a class="keyword" href="http://d.hatena.ne.jp/keyword/Common%20Lisp">Common Lisp</a> が良いと思ったんですがちょっと難しそうなので後回しにして、より文法が簡単そうな <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> を選択しました。</p><p>なぜ <a class="keyword" href="http://d.hatena.ne.jp/keyword/Lisp">Lisp</a> を選んだかと言いますと、ずっと私の中に <a class="keyword" href="http://d.hatena.ne.jp/keyword/Lisp">Lisp</a> に対する複雑な感情があったからなんです。</p><p>多くの <a class="keyword" href="http://d.hatena.ne.jp/keyword/Lisp">Lisp</a> 嫌いがそうであるように、「こんな括弧だらけの言語どうやって読み書きするんだ」とか、「読み書きできる連中の気が知れない」みたいな否定的な感情を持つ裏で、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Lisp">Lisp</a> を理解できる気がしない自分は彼らより知的に劣っているんじゃないか、という劣等意識も持っていました。</p><p>そのことを認めたくないが為に <a class="keyword" href="http://d.hatena.ne.jp/keyword/Lisp">Lisp</a> を悪く言う、という典型的な <a class="keyword" href="http://d.hatena.ne.jp/keyword/Lisp">Lisp</a> 嫌いであったわけです。</p><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%B4%D8%BF%F4%B7%BF%B8%C0%B8%EC">関数型言語</a>を勉強しようと思った時に、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Lisp">Lisp</a> も<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B4%D8%BF%F4%B7%BF%B8%C0%B8%EC">関数型言語</a>の一種であることを知り、じゃぁこれを機会に <a class="keyword" href="http://d.hatena.ne.jp/keyword/Lisp">Lisp</a> 嫌いを克服してみよう、と一直線に繋がった感じでした。</p><br /> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> の勉強には主に次の2つのテキストを用いました:<br /> <a href="http://www.ccs.neu.edu/home/dorai/t-y-scheme/t-y-scheme-Z-H-1.html">Teach Yourself Scheme in Fixnum Days</a><br /> <a href="http://mitpress.mit.edu/sicp/full-text/book/book.html">Structure and Interpretation of Computer Programs</a></p><p>驚いたことに、難しい、分からないと思っていた部分 (大量の括弧) は、1 足す 2 を</p> <pre class="code" data-lang="" data-unlink>(+ 1 2)</pre><p>と書くということ、つまり、括弧で囲まれた式の最初の要素がオペレータ(関数)で、残りは引数である、というごくごく簡単な文法を理解した時点で氷解したように思います。</p><p>何だそんなことだったのか、と拍子抜けするような思いでした。もちろんマクロとか継続とか、難解な概念もたくさん在るわけですが、括弧への苦手意識はほとんど一瞬で消え去り、適切なテキストで初歩から丁寧に学んだことで、少しずつ <a class="keyword" href="http://d.hatena.ne.jp/keyword/Lisp">Lisp</a> を理解し受け入れていくことができました。</p> </div> <div class="section"> <h4>ジャンプ</h4> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/SICP">SICP</a> の <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> コードの美しさに魅了され、<a class="keyword" href="http://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a> で <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> っぽいプログラミングを試みたりしながらも、まだ <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> でプログラムを書くには至らない状態が続きました。あくまで <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> は勉強用で、実用のプログラムを書く言語ではないという認識に留まっていたのです。</p><p>と言うか <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> は終えて <a class="keyword" href="http://d.hatena.ne.jp/keyword/Common%20Lisp">Common Lisp</a> の勉強に入っていました。</p><p>そんな折、とあるウェブサイトに遭遇したことが事態を一変させます。</p><p>これです。<br /> <a href="http://www.flickr.com/photos/reinyannyan/3027199718/" title="toro1 by reinyanluv, on Flickr"><img src="http://farm4.static.flickr.com/3059/3027199718_3d00d7c748_o.png" width="800" height="600" alt="toro1" /></a></p><br /> <p>スクロールして右下を見ると…</p><p><a href="http://www.flickr.com/photos/reinyannyan/3027199672/" title="toro2 by reinyanluv, on Flickr"><img src="http://farm4.static.flickr.com/3231/3027199672_7e33f00e4c_o.png" width="800" height="600" alt="toro2" /></a></p><br /> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Gauche">Gauche</a>!?!?</p><p>これを見た時、心の中で叫びました。「いた!」と。「<a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> でプログラムを書いてる人がいた!」</p><p>その衝撃から、「よし、自分も」と発奮させられ、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> でプログラムを書くことを決意した、というわけです。2007年5月のことでした。</p><p>このサイトに出会っていなかったら Schemer になるのはずっと遅れていたか、あるいは、なっていなかったかもしれません。</p><p>何がきっかけになるか分からないもんですね、というお話でした。</p> </div> Sat, 15 Nov 2008 00:00:00 +0900 hatenablog://entry/17680117127059126476 恒等関数としての values https://reinyannyan.hatenadiary.org/entry/20081107/p1 <p>再び多値関連の小ネタです。</p><p>PLT <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scheme">Scheme</a> のあるライブラリを見ていて、こんな values の使い方を発見しました。</p> <pre class="code" data-lang="" data-unlink>(filter values (list ...))</pre><p>一瞬 values が再定義されているのかな?と思って辺りを探したんですが何も無く、試しにやってみると</p> <pre class="code" data-lang="" data-unlink>&gt; (filter values &#39;(1 2 #f 3 4 #f 5)) (1 2 3 4 5)</pre><p>おお。</p> <pre class="code" data-lang="" data-unlink>&gt; (map values &#39;(1 2 #f 3 4 #f 5)) (1 2 #f 3 4 #f 5)</pre><p>なるほど。</p><p><a href="http://d.hatena.ne.jp/reinyannyan/20081029/p1">&#x524D;&#x56DE;</a>驚いた時と同様、「多値」イコール「2つ以上の値」という<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B8%C7%C4%EA%B4%D1%C7%B0">固定観念</a>から抜け出せていなかったようです。このように1つの値に適用された場合は、ただの恒等関数 (\x = x) として機能するわけですね。</p><p>じゃぁ0値の場合はどうなるんでしょうか?</p> <pre class="code" data-lang="" data-unlink>&gt; (values) &gt;</pre><p>REPL での見かけ上は void 値を評価した時と同じです:</p> <pre class="code" data-lang="" data-unlink>&gt; (void) &gt;</pre><p>ということは?</p> <pre class="code" data-lang="" data-unlink>&gt; (void? (values)) context expected 1 value, received 0 values === context === d:\MzScheme\collects\scheme\private\misc.ss:68:7</pre><p>おぉ、void ですら無い、と。</p><p>じゃぁこんな風に0引数関数を呼ぶこともできる、ということですね</p> <pre class="code" data-lang="" data-unlink>&gt; (call-with-values (lambda () (values)) (lambda () &#39;nullary)) nullary</pre><p>理解しました。</p><p>応用すると、適当に値を集めてきてその数によって分岐処理をする、みたいな事も可能になるわけです:</p> <pre class="code" data-lang="" data-unlink>(call-with-values (lambda () (apply values variable-length-list)) (case-lambda (() &#39;nullary) ((x) &#39;unary) ((x y) &#39;binary)))</pre> Fri, 07 Nov 2008 00:00:00 +0900 hatenablog://entry/17680117127059126593 Scheme