リンクリスト
全三回プラス番外編になっています。
CPythonに後置インクリメントを加えてみた 概要とまとめ
CPythonに後置インクリメントを加えてみた 実装編
CPythonに後置インクリメントを加えてみた 全変更点一覧
CPythonに後置インクリメントを加えてみた 番外編
番外編:リスト内包表記をHaskellっぽくする
本来インクリメントはリスト内包表記の変更の練習のための課題であった。しかし、インクリメントが設計思想にそぐわないという背景もあってか予想以上にその実装には苦労した。リスト内包表記の変更は構想はともかく、いままでの知識があれば五分ほどで終了する。
まず、変更を独立させるためにこちらはDOSS_INCREMENTではなく、DOSS_HASKELLを用いる。したがってconfigureは以下のようになる。
CFLAGS="-O0 -g -DDOSS_INCREMENT=1 -DDOSS_HASKELL=1" ./configure --prefix="/home/denjo/piyothon_install"
仕様
現状のPythonのリスト内包表記は以下である。
lst = [x for x in range(10) if x >= 5]
一方Haskellは以下である。
lst = [x | x <- [0..9], x >= 5]
改造後のPythonは以下のように表せるようにする。
lst = [x $ x <- range(10), x >= 5]
なおここで|
ではなく$
を用いているのは、|
は論理和としての演算を持っているので、そちらとして認識されてしまうからである。
変更箇所
変更箇所は以下の三箇所のみである。
comp_iter: comp_for | comp_if
#ifdef DOSS_HASKELL
comp_for: ('for' | '$') exprlist ('in' | '<-') or_test [comp_iter]
#endif
comp_if: ('if' | ',') test_nocond [comp_iter]
#ifdef DOSS_INCREMENT
#define INCREMENT 58
#define PRE_INCREMENT 61
#endif
#ifdef DOSS_HASKELL
#define LARROW 59
#define DOLLAR 60
#endif
case '<':
switch (c2) {
case '>': return NOTEQUAL;
case '=': return LESSEQUAL;
case '<': return LEFTSHIFT;
#ifdef DOSS_HASKELL
case '-': return LARROW;
#endif
}
break;
変更内容
結局やったことはfor,in,ifの代わりに$,<-,”,”をも許容するようにGrammarを書き換え、トークンを登録しただけである。
因みにこれだけだと、[x**2 $ x in range(10), x % 2 == 0]
などという気持ちの悪い書き方もできる。また、,
のあとの条件式は,
で区切って並べるのではなくand
で繋げなければならないのもご愛嬌。
解説
Q. なぜforやinに対して意味を定義していないのに動くのか。forやinのエイリアスになっているのか?
A. そもそもforやinに対して意味は定義されていない。forに対して、forの後にくるトークンは繰り返しに用いる変数で、そのあとにくるトークンは…、と言ったふうに文法の定義がなされているわけではない。forは単なる区切り文字に過ぎず、その後にくるトークンに対していかなる操作を行うか、ということはforに対してではなく、Grammarにおいてforの次にくるもので定義されている。
comp_iter: comp_for | comp_if
comp_for: 'for' exprlist 'in' or_test [comp_iter]
comp_if: 'if' test_nocond [comp_iter]
上は書き換える前のリスト内包表記に当たるGrammarの部分である。comp_for
を見ると’for’
の後にくる語はexprlist
として呼び出されていることが定義されている。また’in’
の後はor_test
が呼び出されることになっている。したがってどのような定義(構文木がどのように作られ、どのようなバイトコードが実行されるか)は、forやinよりもむしろexprlist
、or_test
、そしてcomp_for
に対して定まっていると言える。
余興:前置インクリメントも実装してみる
新たなオペコードUNARY_PRE_INCREMENTを作成し、ceval.cにおいてスタックに積む順番に注意すれば30分もあれば作成することができる。