Slide 1

Slide 1 text

Scheme Linterを作る Niyarin

Slide 2

Slide 2 text

Linter for Scheme ・作法に反するコードを教えてくれるやつ (if (not (some-pred? a)) (write ‘hello))   Linter 「 に変更しよう」 (unless (some-pred? a) (write ‘hello)) → 例 ・(捕捉)Schemeコーディングスタイル派閥 関数定義(define (foo arg) ...) VS (define foo (lambda (arg) …) あとはifを使わずcond+else勢とか ・Schemeは決まったコーディングスタイルは特にないが、   明らかに冗長なものは潰せる 1

Slide 3

Slide 3 text

Linter実装方針 kibit/kibit/src/kibit/rules/control_structures.clj ・パターンベースのやつ     → ユーザがルールを追加しやすい S式言語はこういうの楽で良いね ・ClojureのKibitもそういう方針 core.logicベース 2

Slide 4

Slide 4 text

作ったもの ・プロトタイプとしてRed-parenという小さいLinterを作った → syntax-rulesライクなルール記述ができる      行数取得とかできるreaderがないので入出力が未完全 ・没案としてmini-kanrenとかの利用も検討していた → Kibitがcore.logic使っているようだったので ・syntax-rulesライクなパターンマッチング(のようなことをする)ライブラリ ・rules (最初に作ったやつ)   ・rules+ (記述力が弱かったので↑を拡張した) niyarin/red-paren niyarin/rules 2

Slide 5

Slide 5 text

最初に作ったライブラリ、Rules ・rulesの例 実質syntax-rulesの関数版のようなもの      inputが第一引数にマッチしたなら第二引数に展開される ・schemeでschemeを書く時にsyntax-rulesの実装にも役立つ er-macro/ir-macro(ハイジニックな評価を使うマクロ)があればすぐ実装できる ・あまり強力ではなかった ・型の判定等は苦手 定数(0,#t,#fなど)の判定はできるが、     数値かどうかの判定などはできない ・繰り返し記号の1listに1回だけという制約 (match-expand (when (not pred) body ...)  (unless pred body ...) input) 3

Slide 6

Slide 6 text

rulesの繰り返し記号の制約 ・1つのリストに繰り返し記号2回使うルールが書けない 0を含む+演算 (syntax-rulesでは再帰的にマクロルールを書くことで対処している) ・メリットとしては計算は少なくてすむ ・バックトラックしなくて良い 繰り返し回数が(入力リストの長さ - 非繰り返し記号)で定まる (+ num1 … 0 num2 ...) (a b c … d e)に(1 2 3 4 5 6)を入力した場合、 入力長6からa,b,d,eの4つを引く 4

Slide 7

Slide 7 text

rulesの実装は必要だったのか ・syntax-rulesのような操作を関数実行時に行う一番楽な方法 ・ほとんど使われないのに必要な時に忘れられるeval ・10行足らずで書けてしまったよ (define (match-expand rule template input) (eval `(let-syntax ((expand (syntax-rules () ((_ ,rule) (quote ,template)) ((_ _ …) #f)))) (expand ,input)) (environment '(scheme base)))) 5

Slide 8

Slide 8 text

rules+ (1) 繰り返し数の制限を無くした ・ … 記号は最長マッチの繰り返しとして扱うように変更した ・継続でバックトラックするだけなのでそれほど労力はかからなかった。 6 (or (call/cc (lambda (break) 繰り返しにもう一回マッチする)) 繰り返しおわり、次に進む) (break #f) マッチ失敗した場合

Slide 9

Slide 9 text

rules+ (2) 述語埋め込み 述語を埋め込めるようにした 挙動 ・述語に入力を適用して#tならば述語:入力の連想リストで登録される ・複数回使う場合はラップして別ポインタに配置される必要がある 手続きのポインタ配置は仕様の外なので        コンパイラの最適化にひっかからないようにする `((assv (quote ,symbol?) als) (assq (quote ,symbol?) als)) (let ((symbol1? (lambda (x) (and (symbol? x) 1))) (symbol2? (lambda (x) (and (symbol? x) 2)))) `((some-rule ,symbol1? ,symbol2?) (some-template ,symbol1? ,symbol2?))) 7

Slide 10

Slide 10 text

Red-parenの実行例 ---------- (if (not (and (> x 0) (= (modulo (+ x y) 2) 0))) (display (car (car y)))) => (unless (and (> x 0) (= (modulo (+ x y) 2) 0)) (display (car (car y)))) ---------- (> x 0) => (positive? x) ---------- (= (modulo (+ x y) 2) 0) => (even? (+ x y)) ---------- (car (car y)) => (caar y) (if (not (and (> x 0) (= (modulo (+ x y) 2) 0))) (display (car (car y)))) > gosh rparen.scm badcode1.scm 結果 入力 8

Slide 11

Slide 11 text

Red-parenのチェック項目例 ・SRFI/R7RS large用のリストとか append-map、concatenate、remove `((apply append (map f ls ...)) (append-map f ls ...)) ((apply append list-of-list) (concatenate list-of-list)) ((filter (lambda (x) (not (pred x)) body) ls) (remove pred ls))) 9

Slide 12

Slide 12 text

おわり/あとでやること 今後実装する手続き(ライブラリ) ・行数とか頭の空白情報を保持するreader   ・いいかんじに成形するwriter 出力のフィルタリング やってみたいこと ・初心者が書いたSchemeコードを入力してみたい AtcoderとかQiitaに転がってそう 典型的な冗長なコードからチェックパターンを増やせると思う   10