kiyokaのブログアーカイブ

Archive of old blog posts

副作用の無いコードの静的解析について(1)

だいぶ前に、[Nendo]に型検査を入れて副作用の無いコードを静的解析で保証したいという話題を書いた。 ( 2011-10-07Nendo*Ruby コードブロックに副作用が混在するかを検出できるかどうか ) あれから時間が経ったけど、諦めずに考えているのであった。もう2年も経ったのか…

最近TAPL(第8章、第9章)を読んで、理論的なところを知ると、いろいろ気づいたので自分用のメモとして書いておく。

欲しい機能

[Nendo]にどんな機能を追加したいかというと、

(pure S式)

と書くと、コンパイル時に静的解析して「S式の中に副作用が無いことを保証してくれる」機能だ。

例えば、次のコードは静的解析で実行前にエラーとなる。

(pure
 (define a 0)
 (set! a 1))

次。func1からの関数呼び出しがどれだけ深かろうが静的に検査して欲しい。

(pure
 (func1))

また、次のコードは副作用が無いと判断し、エラー無しで通って欲しい。

(pure
 (define lst '(1 2 3))
 (define result
   (map
    (lambda (x)
      (+ x 1))
    lst)))

ただ、そう簡単にはいかないのが世の中というもの。

問題点

  • アノテーションの記述量が膨大になる

静的解析するためには、事前に型情報のようなものが必要となる。 型情報といっても、副作用が無いと考えられる関数全てに「pure」の注釈を付けるだけなので、StringやIntegerなどの型情報は不要だ。 最低限、pureな全ての関数には必要。

これはなかなか膨大な作業で、関数を定義するたびに注釈を付ける必要がある。 なんとか半自動でやる方法は無いものかなぁ。

  • 構文にも型情報が必要

if や cond 、let let* などの規定の構文(syntax)にも型情報が必要。 これは関数とは別に特別扱いで処理する必要がある。

  • Rubyのメソッドディスパッチの問題

Nendoは、残念なことに次のような記述を許してしまっている。

(str.chomp)

strという変数がRubyのString型であると判別できないと、chompというメソッドがString#chompだとはわからない。 これは、Nendoが動的型付け言語なのでstrがRubyのどの型なのか簡単にはわからないため。 静的解析できるようにするためには、strの型をアノテーションしてやらないいけないが、こいつはプログラマに余分な手間をかけさせてしまう。 お手軽にやりたいので、アノテーション無しにしたいところ。

解決策は、(str.chomp)のような 「. (ドット)によるメソッドコンビネーションは使えない」とすることかな。 その場合は、pureなchomp関数を定義する必要がある。

(chomp str)

自分が過去に書いたコードでも、str.sizeは多様している気がするので、そこは書き直す必要がある。

その他(備忘録)

  • デバッグ出力は特別扱い
    #?=S式
    

    という形式のデバッグ出力は、標準エラー出力にデバッグ表示するという副作用があるのだが、気軽にデバッグ出力を入れたいので型検査結果として「副作用なし」としたい。

おわりに

[Nendo]はSchemeのサブセットであり、最初から静的型付けの言語とは反対の性質を持っているので、後付けでどこまでできるかという問題になりそうだ。 まあ、実際にはやってみないと何が出るかわからんというところですな。 ただ、JavaScript界隈では先駆者が多く存在し、JSX、Dartは型はoptionalで、指定が無ければ型推論で補うようになっているなど、参考にできるものはたくさんありそう。 TAPLを読むだけでなく、先行技術も調査しないといけないなぁ。