Scheme:イテレータスタイル
算譜の記 にあったちょっとしたテキスト処理。引用すると:
<ID> <keyword1> <keyword2> ... <keywordi> のようなフォーマットのレコード行が複数あるテキストデータ(各行のkeyword の数は可変)を <ID> <keyword1> <ID> <keyword2> ... <ID> <keywordi> のように ID と <keyword> との対を1行とするテキストデータに変換したい
Haskellだと2行でいけるらしい。Schemeではそうはいかない。
(use srfi-13) (port-for-each (lambda (x) (let ((y (string-tokenize x))) (unless (null? y) (for-each (lambda (z) (format #t "~a\t~a\n" (car y) z)) (cdr y))))) read-line)
SRFI-13のstring-tokenizeと、非標準のport-for-each, read-lineを使ってる。
これをコーディングしてて思ったのだが、繰り返し処理って普通、 「これこれの各要素に対してこの処理をする」と考えるのに対し (英語でも "for each element of this collection, do this procedure")、 Schemeのfor-eachやmapは先に処理が来て次に集合が来る。 複数の集合へのmap処理を考えてのことだろう。 (英語ならmapの場合は "map this procedure on (each element of) this collection" とも言えるけど)。
処理をインラインでlambdaで書く場合、どうしても複数行に渡ることが 多く、なんとなく思考の流れとテキストが一致しないんだなあ。
Rubyのイテレータ、集合.each { 処理内容 } のような順序の方が 直感的に書いてくのに良いように思う。特に使い捨てのスクリプトを 書くときなんかは。
但しそれだと複数のコレクションに対するマップ操作は書きにくい。
- 算譜の記 に追記が。object -> method という流れは確かに日本語に似てるかも。
- ところでこの順番はUnixシェルで処理をつなげてゆくのと同じ流れなわけだが、 直接プロンプトにコマンドを直書きするようなシェル言語ではこの順番が 重要なんじゃないかと思う。頭で思った通りに書けて、戻る必要がない。 Schemeなんかは、処理をつなげてゆくときにどうしても行きつ戻りつするから、 エディタじゃないときつい。EmacsみたいにS式単位で行きつ戻りつが できて、かつ式をすぐにインタプリタに送れるようになってないと、 Rapid prototypingのメリットが享受できないんじゃないかと思う。
- S式の括弧を閉じるとカーソルがS式の前にくるような行編集機能が、 ShellやSchemeのインタープリタモードに備わっているといいですね。--nobsun
- vi-modeは、いかかでしょう?(vi-modeの%)--silk_warm_eel
- Lispマシンとかはエディタとシェルが不可分に結び付いて いたんじゃないかと思います。そういう環境ならS式は 自然なインタフェースであったでしょうねえ。私は Lispマシンそのものは見ただけで触ったことがないのですが。--Shiro
- Perl だと 1 行です。--pa
print map { ($id, @keys) = split /\s/; map { "$id $_\n" } @keys } @data;
- Perlは '$_' の存在とオペレータ名が短いのが強みですね。 Gaucheだと頑張ってここまで。やっぱり 'lambda' って長い。--Shiro
(for-each (lambda (l) (for-each (cut print (car l)" "<>) (cdr l))) (port-map (cut string-split <> #/\s+/) read-line))
Arcでは 手続き以外のオブジェクトも適用可能にしようというアイディアがある。 例えばベクタをオペレータの位置に持って来て引数にインデックスを渡して 要素を取り出すとか。
('#(1 2 3) 1) ==> 2 ("abc" 1) ==> #\b (apply "abc" '(2 1 0)) => '(#\c #\b #\a)
Gaucheにもこのアイディアを入れようかと思っていて、具体的には object-applyみたいなメソッドを定義しておくと、そのオブジェクトが applyされたときにそれが呼ばれると。
(define-method object-apply ((obj <vector>) (index <integer>)) (vector-ref obj index))
これで、
(define-method object-apply ((obj <vector>) (mapper <procedure>) (proc <procedure>)) (mapper proc obj))
とかしておいたら、Ruby的なイテレータの書き方もできるかもしれん。
こんにちは。 Backus' FFP systemにある Metacomposition Rule というのをからめると、なにか面白いものができそうな 気がするんですが、具体的なこれだ!というのが思いつきません。 どうでしょう。 --SHIMADA@2002/09/03 20:17:17 PDT
↑FFP system、なるほど、この規則でもって シーケンスというデータが評価可能な式へとliftされるわけですな。 でもシーケンスの最初をオペレータとしてしまっているのは… もともとリストの最初を特別扱いするのはLispでは 組込み機能みたいなもんなんで、あんまり嬉しくないかも。 --Shiro (2002/09/04 02:45:12 PDT)
- そうですね。関数が最初にないといけないならLispでも関数呼び出しフォームそのままですね。うーん残念。--SHIMADA (2002/09/04 20:29:51 PDT)
難しいシステムですねぇ。apply と ':' が区別がよくわからないでいます。--nobsun (2002/09/04 05:00:41 PDT)
(define (seqapply fs x) (map (lambda (f) (f x)) fs)) (define (seqcompose fs x) (fold (lambda (f y) (f y)) x fs))
とかじゃだめかな
- 断片的かついいかげんな説明ですみません。簡約過程の説明のために<f:x,g:x,h:x>とか書いてますが、実際はこういう式が書ける訳ではないんですよ。':' は式の中に functional-form ':' object という形で一回しかあらわれません。--SHIMADA (2002/09/04 20:29:51 PDT)