見出し画像

Retro-gaming and so on

RE: プログラミング学習日記 2024/03/04〜

龍虎氏の記事(2024/03/06)に対するコメント。

ジェネレータに関するScheme/Racketでの解釈は色々ある。
Pythonのジェネレータに関しての、典型例は例えば次のようなモノがある。

def generator():  # yield文によって正の整数を返す関数
 n = 0
 while True:
  yield n
  n += 1

使用例は次のようになる。

>>> gen = generator() # 正の整数を返すジェネレータを生成
>>> next(gen)
0
>>> next(gen)
1
>>> next(gen)
2

このジェネレータは延々と1づつ増えた整数を返していく・・・・・・。
そう、一つの解釈としては、実はPythonのジェネレータはScheme/Racketで言うトコの遅延評価なんだ。

;; Racket/Schemeの場合
> (require srfi/41)
> (define *gen* (stream-from 0))
> (stream-ref *gen* 0)
0
> (stream-ref *gen* 1)
1
> (stream-ref *gen* 2)
2

もっと正確に言うと、Pythonのジェネレータは遅延評価そのもの、と言うより遅延評価のダウングレード版だ。
遅延評価のデータ構造はそもそも永続的だが、Pythonのジェネレータはそうじゃない。破壊的に変更されていく。
つまり、元のPythonのコードはRacket/Schemeではある意味次のコードを意味してる。

(require srfi/41)


(define (generator)
 (let ((strm (stream-from 0)))
  (lambda ()
   (begin0
    (stream-car strm)
    (set! strm (stream-cdr strm))))))

実行例は次のようになる。

> (define gen (generator))
> (gen)
0
> (gen)
1
> (gen)
2
>

かなりPythonの動作に近づいていってる。ある意味Racket/Schemeの持ってる「遅延評価」によるデータ構造(ストリーム)を破壊的変更によって制限を付けたクロージャを作る事によって実現してるわけだ。
もっとPythonに寄せると次のようにも書けるだろう。

(require srfi/41)


(define (generator)
 (let ((strm (stream-from 0)))
  (lambda (arg)
   (case arg
    ((next) (begin0
        (stream-car strm)
        (set! strm (stream-cdr strm))))))))

Scheme/Racketによる「オブジェクト指向の仕組み」を思い出そう。この記述で関数generatorが生成するクロージャはnext、と言うメソッドを持つ。

> (define gen (generator))
> (gen 'next)
0
> (gen 'next)
1
> (gen 'next)
2

PythonのイテレータやジェネレータはScheme/Racketの知識で解釈すると遅延評価、あるいは遅延評価をダウングレードしたモノ、って捉えた方が分かりやすいと思う。
Pythonで「無限」を記述する場合はイテレータやジェネレータに頼る。同じ構造をRacket/Schemeでは「ストリーム」と言う形式で記述出来、それはPythonのイテレータやジェネレータより遥かに強力で「正しい」機能なんだ。
  • Xでシェアする
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

最近の「RE: プログラミング学習日記」カテゴリーもっと見る

最近の記事
バックナンバー
人気記事