Upgrade to Pro — share decks privately, control downloads, hide ads and more …

CombGig3 発表資料

shibh308
September 09, 2018
95

CombGig3 発表資料

言語を自作したかったおはなし

shibh308

September 09, 2018
Tweet

Transcript

  1. 2   はじめに • この発表では静的型付けでラムダ計算を実装していた話をします (“ ” 言語を自作 はうそです ごめんなさい

    ) • 簡単な説明と実装、後は提出コード例をいくつか載せるつもりです • … 発表者の知識がガバガバなのでおかしい点があるかもしれません • JOI 夏季セミナーの内容が ( 一部 ) 含まれます もう見た人はごめんね
  2. 3   自己紹介 • 塚本 (@shibh308) • AtCoder: 1685 •

    JOI17-18 予選 B ランク!! • PCK18 予選落ち ( 予定 )
  3. 4   自己紹介 • 塚本 (@shibh308) • AtCoder: 1685 •

    JOI17-18 予選 B ランク!! • PCK18 予選落ち ( 予定 )
  4. 5   ラムダ計算について • ラムダ計算 ... 計算体系のひとつ 関数型プログラミング言語の基盤になっている ( らしい

    ) • 以下の構文 (BNF 記法 ) で定義されている ( らしい ) ・ <expr> ::= <identifier> ・ <expr> ::= (λ<identifier>. <expr>) ( ラムダ抽象 ) ・ <expr> ::= (<expr> <expr>) ( 関数適用 ) • 細かい説明をすると詳しいプロに殺されそうなので省略
  5. 9   言語全体の仕様について • 型付きでラムダ計算を実行できるようにする • 基本的な部分は型無しの場合とあまり変わらない • プリミティブな型として bool(B)

    と nat(N) を扱えるようにする • タプルを実装して、それを引数にとった四則演算も組み込みで用意する • ラムダ抽象の記号は @ で、 @ 名前 : 引数の型 .( 処理 ) のように書く • $ 名前 ( 処理 ) でいい感じの宣言ができる $v(5) (@x:N.add (3, x)) v のように記述できる
  6. 10   実装について • C++17 で書いていく (std::variant を使うため ) •

    入出力の部分は実装が辛いのでそこは C++ に投げる • パースや型検査時のエラーは箇所や内容を表示させる せっかく静的に付けてるのでここはちゃんとやりたい
  7. 12   構文解析について • std::string で受け取った文章を気合でパースする • 仕様に沿って構文木のノードを生成して、子を shared_ptr で持つ

    • 問題が発生した場合は parseError を吐いて終了させる • 解析した結果は json 形式で出力して可視化できるようにした • すごい面倒
  8. 13   型検査について • 構文解析後に変数の型 (bool,int 等 ) が正しいかを DFS

    で調べる • 検査時点で型が定まっていない場合も子を再帰的に見る • tuple の場合は tuple 内の型が全て正しいかを検査する • 型が正しくなかったらエラーを吐いて終了させる • これをするとランタイムエラーがすごい減る うれしい
  9. 14   実行について • なんとなく簡約をしてから評価をしたくなるので仮にそうする • まラムダ抽象に対する関数適用か let 束縛をするノードを探す •

    その後、その名前とマッチしている部分を置き換える DFS をする • あとは関数適用を評価していけばそれで演算結果が取得できる (@x:T.expr) v を [x->v](expr) にする
  10. 15   実際の実装について • 構文木のノードはクラスとして実装する   ・構文の形式 ( 抽象 ,

    適用とか )   ・返り値の型 ( 型検査の時に用いる )   ・構文本体の内容   ・子ノードの shared_ptr の配列                   とかを持つ事にする • “15” ならこれらの内容は { 値 ,nat,15,{}} とかになる
  11. 16   実際の実装について • 構文木の生成時には返り値が決まっていない時がある ( 適用など ) • その場合は型検査の時に子ノードから返り値の型を決定する

    • 例 : “succ 1” ” は succ” ” と 1” を子に持つノード       →  返り値の型がまだ決まっていない 型検査時に型を決定する     succ は nat を引数にして nat を返す関数         →” succ 1” の返り値は nat になる
  12. 17   実際の実装について • 組み込みの関数 ( 四則演算など ) をいちいち特別処理するのは厳しい  

    →組み込み関数の設定を簡単にできるようにしよう! • 組み込み関数の定義に必要なパラメータをまとめる構造体を作った   ・名前   ・引数の型   ・返り値の型   ← ・関数本体 型の取りうる値が複数ある std::function<hoge(fuga)>
  13. 18   実際の実装について • 組み込みの関数 ( 四則演算など ) をいちいち特別処理するのは厳しい  

    →組み込み関数の設定を簡単にできるようにしよう! • 組み込み関数の定義に必要なパラメータをまとめる構造体を作った   ・名前   ・引数の型   ・返り値の型   ←   ← ・関数本体 型の取りうる値が複数ある variant を使おう! std::function<hoge(fuga)>
  14. 19   関数の実装について • 組み込み関数を表す構造体は   ( 関数名 , 引数の型

    , 返り値の型 , 関数 ) で宣言できる • 構造体の第四引数には を指定する!!!!!! std::variant<std::function<void(std::variant<int,bool>&)>, std::function<void(std::vecto
  15. 24   オンラインジャッジでの C++17 対応 • この機会に各サイトで C++17 が使えるか調べた •

    AtCoder: 不可 • AOJ: 不可 • Codeforces: 可 • CSA: 不可 • yukicoder: 可
  16. 25   提出してみた • yukicoder なら動く事が分かったので、 ( 他の問題を ) やってみる

    • 提出して AC できた! (https://yukicoder.me/submissions/278956)
  17. 26   書いたプログラムの問題点 • 内容に対してコード量が多すぎる (1000 行ちょっと ) • 再帰が死んでる

    ( 結局実装が間に合わなかった 致命的 ) • C++17 なので AtCoder で使えない (variant 無しでもできそう ) • その他 ( タプルの要素参照とか ) も使い勝手が結構ヤバい
  18. 27   書いたプログラムの問題点 • 内容に対してコード量が多すぎる (1000 行ちょっと ) • 再帰が死んでる

    ( 結局実装が間に合わなかった 致命的 ) • C++17 なので AtCoder で使えない (variant 無しでもできそう ) • その他 ( タプルの要素参照とか ) も使い勝手が結構ヤバい   →書き直すしかない!!!
  19. 28   実装をやり直す • C++14 で書き直した (variant いらなかった ) •

    パースが面倒なので tuple の宣言を &(a,b) みたいにした • 入出力を ( 内部で cin,cout 使って ) 実装した • 型検査器の実装がどっかいった
  20. 29   再帰の実装 • ここなんかすごい辛かったです • $f(@x.f x) を $f(@x.$f2(@y.f2

    y) f2 x) にするみたいなのをやった • 再帰させる度に木が深くなってしまうので、速度がヤバい ( 後述 )
  21. 30   問題を解こう! • C++14 なので AtCoder に提出できます うれしいね •

    問題解くためのコードは埋め込み、標準入力は変数に読み込む • がんばれば ABC-C ぐらいまではできるので、やります
  22. 35   提出コード “$a(&(0)) $dp(&(0)) $n(0) $min(@x.if &((< &(get &(x,

    0), get &(x, 1))), get &(x, 0), get &(x, 1))) $diff(@x.if &((> &(get &(x, 0), get &(x, 1))), sub &(get &(x, 0), get &(x, 1)), sub &(get &(x, 1), get &(x, 0)))) evalback &(assign &(n, in &(%n)), assign &(a, mktup &(n, 0)), ($inp(@x.if &((any &(sub &(x, n))), evalback &(set &(a, x, in &(%n)), inp (add &(x, 1))), 0)) inp 0), if &((eq &(n, 2)), out &(diff &(get &(a, 0), get &(a, 1)), %N), evalback &(assign &(dp, mktup &(n, 1000000000000)), set &(dp, 0, 0), ($loop(@k.if &((eq &(k, n)), 0, evalback &(if &((> &(k, sub &(n, 2))), 0, set &(dp, add &(k, 1), min &(get &(dp, add &(k, 1)), add &(get &(dp, k), diff &(get &(a, k), get &(a, add &(k, 1))))))), if &((> &(k, sub &(n, 3))), 0, set &(dp, add &(k, 2), min &(get &(dp, add &(k, 2)), add &(get &(dp, k), diff &(get &(a, k), get &(a, add &(k, 2))))))), loop (add &(k, 1))))) loop 0), out &(get &(dp, sub &(n, 1)), %N))))”
  23. 36   原因について • 再帰が遅すぎて 10^5 オーダーの繰り返しができない   →入力読み込みで TLE

    している • 小さいケースは通っている   →解法自体はあってそう 結論 : 想定解が通らない
  24. 39   おまけ • A: むり 文字列の出力を実装していませんでした ( 人生終了 )

    • B: むり 文字列の入力を実装していませんでした ( 人生終了 ) • C: むり 10^5 オーダーの入力なんて受け取れません O(N) なんて無理 • D: むり 500*500 は 250000 なのですが、これも入力だけで TLE します