ヒビノキロク RSSフィード

Firefox 3 - The best Firefox yet RSS feed meter Creative Commons License

移転しました。現在ははてなグループの方で細々と更新しています。(移転理由)

2003 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2004 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2005 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2006 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 02 | 05 |

2007/02/11 (Sun)

リストの内包表記とlambda

| 17:49 | リストの内包表記とlambdaを含むブックマーク リストの内包表記とlambdaのブックマークコメント

AOISAKURA - 日記(2007-02-08) リストの内包表記へのコメント。

コメント先で例に挙がっている

[x * 2 for x in range(3)]

は、Lispで書けばこうかな?

(map (lambda (x) (* x 2)) (range 3))

Lispに慣れた立場からは、リストの内包表記はlambdaは使ってないけどmap+lambdaと等価なものに見えます。むしろforとかinとかの構文的キーワードが少ない分、lambdaを使った下の方が分かりやすいと感じたり…(ほとんど病気ですね)

これだけだと単なる好みの話になってしまうので、Schemeでリストの内包表記ができるようなマクロを考えてみました。

(use srfi-1)

(define-syntax range
  (syntax-rules ()
    ((_ count) (range1 count))
    ((_ start end) (range2 start end))
    ((_ start end step) (range3 start end step))))

(define (range1 count)
  (iota count))

(define (range2 start end)
  (iota (- end start) start))

(define (range3 start end step)
  (iota (ceiling (/ (- end start) step)) start step))

(define-syntax list-comprehension
  (syntax-rules (for in)
    ((_ form for var in lst)
     (map (lambda (var) form) lst))
    ((_ form for var in lst rest ...)
     (append-map (lambda (var) (list-comprehension form rest ...)) lst))))

実行例

% gosh
gosh> (load "./list-comprehension.scm")
#t
gosh> (list-comprehension (* x 2) for x in (range 3))
(0 2 4)
gosh> (list-comprehension (+ x y) for x in (list 2 4 6) for y in (list 4 3 -9))
(6 5 -7 8 7 -5 10 9 -3)
gosh>

実行例(参考)

% python
>>> [x * 2 for x in range(3)]
[0, 2, 4]
>>> [x + y for x in [2, 4, 6] for y in [4, 3, -9]]
[6, 5, -7, 8, 7, -5, 10, 9, -3]
>>>

追記

shiroさんからコメントを頂いてしまった(ドキドキ)。

参考に挙げられている1つ目のページには、Schemeでリストの内包表記を実装した例が載っていました。そういえば昔読んだ記憶があるような…。自分の書いたものと比べると大分複雑になってますが、その分幅広い記法に対応していたり効率に気を遣っていたりするのでしょうね。

SRFIになっていたのは知りませんでした。しかもGaucheにも既に入ってるし。

Schemeで実際に使うならこれが一番だと思います。今回はできるだけPythonの文法に従ったものにしようとして、ああなりました。

追記2

Python チュートリアルを見ていて知ったのですが、

[3 * x for x in [2, 4, 6] if x > 3]

みたいな書き方もできるんですね。ということでそれにも対応したバージョン。

(define-syntax list-comprehension
  (syntax-rules (for in if)
    ((_ form for var in lst)
     (map (lambda (var) form) lst))
    ((_ form for var in lst if cond rest ...)
     (list-comprehension form for var in (filter (lambda (var) cond) lst) rest ...))
    ((_ form for var in lst rest ...)
     (append-map (lambda (var) (list-comprehension form rest ...)) lst))))

実行例

gosh> (list-comprehension (* 3 x) for x in (list 2 4 6) if (> x 3))
(12 18)
gosh> (list-comprehension (list x y) for x in (list 2 4 6) if (not (= x 4)) for y in (list 4 3 -9) if (positive? y))
((2 4) (2 3) (6 4) (6 3))

ちなみに、この2つの例を普通にSchemeで書くならこんな感じになります。

(map (lambda (x) (* 3 x))
     (filter (lambda (x) (> x 3)) (list 2 4 6)))

(let ((lst1 (filter (lambda (x) (not (= x 4))) (list 2 4 6)))
      (lst2 (filter positive? (list 4 3 -9))))
  (append-map (lambda (x) (map (lambda (y) (list x y)) lst2)) lst1))

ここまで複雑になるとどちらが良いかは微妙なところですが、自分的にはまだ普通に書いた場合の理解しやすさの方が、新たな構文を導入するメリットに勝ります。

追記3

そういえば、上の1つ目の例をPerlで書くとこうなりますが、

@a = map { $_ * 3 } grep { $_ > 3 } (2, 4, 6);

これはSchwartzian Transform(シュウォーツ変換)の形にも少し似てますね。

shiroshiro 2007/02/11 19:16 参考:
list-ofマクロ http://practical-scheme.net/wiliki/wiliki.cgi?Scheme%3a%e3%83%9e%e3%82%af%e3%83%ad%e3%81%ae%e5%8a%b9%e7%94%a8#H-b9epxs
gosh> (list-of (* x x) (x in (upto 0 2)))
(0 1 4)

eager comprehension
http://srfi.schemers.org/srfi-42/srfi-42.html
gosh> (list-ec (: x 3) (* x x))
(0 1 4)

さくらいさくらい 2007/02/12 09:25 なんだか長大な話に...。
結局趣味の話になってしまいますが、pythonのmapとlambdaで簡単なモノなら同様に

% python
>>> a = map( lambda x: x * 2, range(3))
>>> a
[0, 2, 4]

まぁ、pythonのlambdaは単一の文しか書けないのでここまでですが。

nozomnozom 2007/02/12 16:38 思わぬ展開に自分でもびっくりです。もともとはさくらいさんの日記にコメントしようとしてまた弾かれたのでこっちで改めて書いたのですが、そのときに追加したマクロの話からまさかここまで発展するとは。

リストの内包表記についていろいろ調べていて思ったんですが、これが便利なのは多変数の組み合わせから条件を満たす解を探索するような場合じゃないでしょうか(典型的なのは http://practical-scheme.net/wiliki/wiliki.cgi?Scheme%3aTaxiNumber にあるようなパズルを解くプログラムとか)。だから変数が1つとか2つとか少ないうちは、そのありがたみがいまいち実感できないのかもしれません。

さくらいさくらい 2007/02/13 16:40 例として教えていただいたのをpythonで書いてみました。

>>> [(a, b, c, d) for a in range(50) for b in range(a+1, 50) for c in range(a+1, b) for d in range(c+1, b) if a**3 + b**3 == c**3 + d**3]
[<出力省略>]

なるほど、確かにこういう時は便利ですね。ただこうつらつら長くなると読みにくいぃぃ...。

とはいえ、内包表記がこんなことできるものだとは知りませんでした。少し、いや結構感動(何

pykenaxtpykenaxt 2007/04/09 21:56 とおりすがりの、pykenaxtです。
さくらいさんがかかれている内包表記は、以下の様にしたら読みやすいかも。

[
(a, b, c, d)
for a in range(50)
for b in range(a+1, 50)
for c in range(a+1, b)
for d in range(c+1, b)
if a**3 + b**3 == c**3 + d**3
]

トラックバック - http://d.hatena.ne.jp/nozom/20070211/1171183754