リストの内包表記と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(シュウォーツ変換)の形にも少し似てますね。