Skip to content
This repository has been archived by the owner on Aug 2, 2018. It is now read-only.

[アイディア求む] rime 作り直し #9

Open
hiroshi-cl opened this issue Dec 20, 2016 · 14 comments
Open

[アイディア求む] rime 作り直し #9

hiroshi-cl opened this issue Dec 20, 2016 · 14 comments

Comments

@hiroshi-cl
Copy link
Member

rime を作り直しましょう。理由としては以下のようなものがあります。

  • 開発時とは作問事情が大きく変わっていてますますいろいろな機能を追加する必要がある
  • メタプログラミングをかなり濫用しているのでアーキテクチャが複雑すぎる
  • Python 2 なので Python 3 への移行が必要
  • config が JSON とかではなく生 python なのでエディタとか周辺ツールの整備がやりづらい
  • 環境合わせ面倒なのでdockerとかクラウドとかと連携したい
  • 静的型くれ

作り直しに使う言語の候補としては以下があります。

  • Python3: Python 2 から移行ということで自然。某社が手を付けているらしい?
  • Go: Windows 専用としてこんなのがある http://qiita.com/Camypaper/items/5b5683a96d013b99d2c3
  • Rust: Goと並んで最近流行りのシステムプログラミング言語で型とかもイケてるらしい
  • ...
@hiroshi-cl hiroshi-cl changed the title rime 作り直し [アイディア求む] rime 作り直し Dec 20, 2016
@nya3jp
Copy link
Member

nya3jp commented Dec 20, 2016

今のコードは黒歴史感があるのでなんとかしたいですね……

メタプログラミングをかなり濫用しているのでアーキテクチャが複雑すぎる
Python 2 なので Python 3 への移行が必要

そのとおりだと思います。

config が JSON とかではなく生 python なのでエディタとか周辺ツールの整備がやりづらい

そうですね。
他の形式だと YAML あたりが妥当かなぁ。JSON はコメントが書けないという致命的な欠点があるので避けたいです。

環境合わせ面倒なのでdockerとかクラウドとかと連携したい

環境合わせって、コンパイラのバージョンとかでしょうか。確かに何かできるといいですね。
今は各プログラミング言語のコンパイラが固定されていますが (C++ なら g++ とか)、これをカスタマイズして Docker コンテナ内でのコンパイルに置き換えられるようにするようなイメージですかね。

開発時とは作問事情が大きく変わっていてますますいろいろな機能を追加する必要がある

わたしは最近ほとんど作問をしていないので、どういう需要があるのか教えてもらえると助かります。
リアクティブな問題のサポートとか?

プログラミング言語の選択

開発のしやすさも重要ですが、使いやすさも大事なのですよね。
Rust はビルドしたものの配布って簡単に出来るんでしたっけ。その点は Python / Go は優れていますね。
あと非 Linux 環境で使っている人がどれだけいるのか気になります。

@hiroshi-cl
Copy link
Member Author

hiroshi-cl commented Dec 21, 2016

JSON はコメントが書けないという致命的な欠点があるので避けたいです。

手書きだとコメント書けた方がいいですね。とはいえ、rimeの設定ファイルに(テンプレに元から書いてあるコメント以外の)コメント書いてる人あまり見たことないですが

環境合わせって、コンパイラのバージョンとかでしょうか。確かに何かできるといいですね。
今は各プログラミング言語のコンパイラが固定されていますが (C++ なら g++ とか)、これをカスタマイズして Docker コンテナ内でのコンパイルに置き換えられるようにするようなイメージですかね。
ですね。

C++だとg++のバージョン問題の他にg++/clang派や、macのニセg++問題とかO2つけるのかとかstd=c++14をつけるのかとかいろいろあります。
その他にもpythonコマンドのバージョン問題とか、マイナー言語のインストール設定の問題とか…
他にもatcoderの言語数の爆発に柔軟に対応したり標準実行環境として揃えるというのもあります。

わたしは最近ほとんど作問をしていないので、どういう需要があるのか教えてもらえると助かります。

リアクティブとか部分点ジャッジとかへの完全な対応とかですかね。これから新しい形式が続々生まれるかもしれないですし、それらへの対応もやりたいですね。あと割とコドフェが変なことをやっているのでそれらの調査もしたいです。

Rust はビルドしたものの配布って簡単に出来るんでしたっけ。その点は Python / Go は優れていますね。

再配布手段が整備されているという点ではやはりGoは強いですね。pythonというかpipは最近 #6 に遭遇したのでちょっと信用してないところがありますが。
最近はパッケージマネージャが多くの言語にもついていて再配布がそれなりにできるようになっています。

あと非 Linux 環境で使っている人がどれだけいるのか気になります。

ガチの競プロ系だと少ないですが、初めて問題準備やってみる人とか業務で問題を準備する場合とかたまに windows 環境がいます。

@nya3jp
Copy link
Member

nya3jp commented Dec 21, 2016

コンパイラを揃える話

一方でコンパイラを揃えるのはあんまり簡単な話ではないのですよね。

  1. Dockerを使う => root 並の権限が必要
  2. Prebuilt binary を使う => 整備が超大変
  3. クラウドでコンパイルする => そんなサービスあったっけ?

基本は今のようにローカルでコンパイル実行する方針にしつつ、コンパイルと実行をカスタマイズできるようにすることで他の実行形態の余地も残すくらいにしたほうがよさそうです。

リアクティブとか部分点ジャッジ

リアクティブはテストデータの代わりにジャッジプログラムそのものを標準入出力に繋げる感じですよね。
部分点ジャッジは、ナイーブな解答プログラムが全部の入力のうちn%くらい解けることをチェックする、みたいなイメージでしょうか。

配布

Python はちゃんと設定すればそんなに問題はないはず。大体の Linux システムにはデフォルトで Python が入っているので、それは強みです。
Rust のパッケージマネージャを使うとして、Rust で書かれたプログラムを実行するためには Rust 自体のインストールから始めないといけない感じでしょうか? それとも生成されたバイナリを配布しても問題ない? (Rust 詳しくないのですみません)

非Linux

問題の準備はけっこう人数を割くので、ユーザの環境やスキルもまちまちなんですよね。
それを考えるとやはりインストールの簡単さというのはすごく重要な要素だと思います。
個人的には Python/Ruby/Perl/Go あたりかなぁという感じです。

@nya3jp
Copy link
Member

nya3jp commented Dec 21, 2016

あと、N年前に受け取ったリクエストとして、ユニットテストを走らせたいというのもありました。

いろんな需要に応えようとすると、じつは結局ほとんど汎用のビルドシステムを作らないといけないのでは、という気もしてきます。
そういったシステムを一から作り上げるのは骨が折れるしオーバーキルっぽいので、本当にそれが必要なのかちゃんと吟味したいところです。
もしやるとしたら、メタビルドシステム (CMake, gyp みたいなもの) を書くのかなぁ、と思っています。

@hiroshi-cl
Copy link
Member Author

コンパイラを揃える話

コンパイルと実行をカスタマイズできるようにする

これによってそこそこ円満に解決できると良かったんですけど、私はatcoder標準イメージ・ICPC標準イメージなどを用意してdockerで走らせるのが本命で、native環境での実行は、諸事情でdockerを使いたくないときなどのために、driverでnative、docker等を選択できる形にしてもいいかなくらいに考えています。それは、あるスタッフの解答が他のスタッフのPCでコンパイルできないという問題は、各人の手元の環境に合わせた設定でコマンドを出し分けるだけでは解決できず、最低限の機能要件を満たしていないと考えているからです。

特に、C++で問題が深刻です。Macだとgccが portsのgcc、brewのgccだけどライブラリがclang系、標準のgccの振りをするclangの上にバージョンが古いという3系統があり、さらにlinux環境の存在やバージョン違いがあります。それぞれコンパイルできるコードがそこそこ違いますが、「インストールの簡単」な範囲では基本的にどれか1個しか1つの環境に用意できません。一方で、スタッフの方はそんなことお構いなく、手元では動くがほかでは動くかよくわからないコードを追加してきます。そのためかなりの頻度で問題が起き、これをそのコードを書いた本人の手で防げるようにすることは円滑な準備においては必要なことではないかと考えています。

rootが必要なことに関しては、そもそも各種処理系を準備する時点ですでに必要なのでそれほど大きなハードルとは考えていないです。

他の案としては、以下のようなものも検討しました。

  1. VM imageを配ってすべてvirtualbox上で、というのは、IDEを使うなどそれぞれのスタッフの開発スタイルにあわせるのは難しいのではないか
  2. dockerを使わず、クラウドのCIサーバ、rime専用サーバみたいなのを用意してそこに投げるというのは、そもそもCLIアプリを捨ててcodeforces polygonみたいにwebサービスを開発するという方向にした方が良いのではないか

@hiroshi-cl
Copy link
Member Author

リアクティブとか部分点ジャッジ

リアクティブはテストデータの代わりにジャッジプログラムそのものを標準入出力に繋げる感じですよね。

大雑把な理解としてはそれで良くて、実際そういう形で作ったものがあります。
https://github.com/icpc-jag/rime-plus/blob/rime_plus/rime/plugins/plus/flexible_judge.py#L72

が、入出力のとり方、終了時の判定の仕方などで幾つかvariantがあってKUPC(atcoder, aojで標準的)、testlib(codeforces?)、NEERC(java使ってるぽい)、それら全部に対応すると色々考えることがありますね。

部分点ジャッジは、ナイーブな解答プログラムが全部の入力のうちn%くらい解けることをチェックする

これは2種類あって、与えられた部分テストケース集合ごとに点数がついていてその合計で点数をつけるものと、(atcoderでは半分隠し機能になっている)カスタムジャッジで点数を計算出力するものがあります。

一応機能としてあり、実際のコンテストでの使用例もあります。
https://github.com/icpc-jag/rime-plus/blob/rime_plus/rime/plugins/plus/subtask.py
ただ、rimeの実行モデルとあまりマッチしていないのと、期待した点数になっているかをチェックする方法とかはどうしようかという感じです。なお、で点数を計算出力する方は実装されているもののatcoderと挙動を合わせることを途中で断念しました。

さらにこれらの合わせ技とかになってくるともうどうしようかな、かなり困った感じです。

@hiroshi-cl
Copy link
Member Author

いろんな需要に応えようとすると、じつは結局ほとんど汎用のビルドシステムを作らないといけないのでは、という気もしてきます。
そういったシステムを一から作り上げるのは骨が折れるしオーバーキルっぽいので、本当にそれが必要なのかちゃんと吟味したいところです。
もしやるとしたら、メタビルドシステム (CMake, gyp みたいなもの) を書くのかなぁ、と思っています。

私はこの認識は正しくないと考えています。ビルドシステムに相当する部分がrimeのコードの中で大きな割合を占めているのは事実だと思いますが、rimeの本質的な部分はその外側の薄皮部分にある問題準備用のカスタムターゲットとカスタムタスクの集合ではないでしょうか。より詳しく言うと、カスタムターゲットとは問題・解答・テストセット・解答コード・入力生成器・入力検証器・出力検証器・…といったものであり、カスタムタスクとはテストケースビルド・解答ビルド・解答テスト・データデプロイといったものです。現行のrimeもそんな感じのアーキテクチャになっていたと思います。

可能であれば、汎用のビルド部分には注力せず、CMake, gyp等をラップして使い、その上に必要な機能をターゲット・タスクとして載せることがrimeの目指すべき方向性なのではないかと思います。

ユニットテストについてはコンテストのための問題準備という対象ドメインから外れているような気もするので個人的にはあまり気乗りはしませんが、需要があれば都度作るべきと考えています。

@hiroshi-cl
Copy link
Member Author

配布・非Linux

インストールの簡単さという観点だと以下の2択になるかと思います。

  1. 作問レポジトリ同梱

  2. 適当なワンライナーでインストール

  3. は昔の rime ですが、この場合Python/Ruby/PHP/Perl/Bashといったどこにでもあるような処理系がよいと言うのはその通りだと思います。また、rime-plusと違ってプラグインを追加しやすかったり、改造しやすかったりしたという利点はあったと思います。

ただ、JAG, KUPC, UTPC, KLab それぞれで派生品ができて秘伝のソースを受け継いでいるという状態になり、本家のアップデートが反映できないといった不都合が出たのであまり好ましいとは考えていません。また、依存ライブラリなどを同梱しないと意味がないので肥大化するといった点もあまり良くなさそうです。

  1. についてはどの処理系でも割と同等だと考えています。Python/Ruby/PHP/Perlが標準的にあるとはいってもpip, gem, cpan 等が最初から入っているかというとそれは怪しいですから。(macだと入ってたりしますが)

最近のイケてる言語ではapt, brew等でインストールできない場合でもcurlとシェルスクリプトを用いたワンライナーが用意されている場合が多いのでパッケージマネージャ等を入れるのにそんなに苦労しないことが多いです。例えば Rust (と同梱されているパッケージマネージャの cargo) は以下のコマンドで入れられます。

curl https://sh.rustup.rs -sSf | sh

なので、インストールの簡単さはあまり心配していません。私としては、それよりもマルチプラットフォームでアプリをインストールが正しく終了し、実行できるかの方に懸念を持っています。

Python/Ruby/PHPといった言語は、多くの環境で標準的に用意されていますが、バージョンの違いがあったりします。スクリプト系言語では、うっかりバージョン依存コードを埋め込む、というかそれ以前に、重大であからさまなエラーがあってもそのパスを通らない限り全くわからないという特性があるので、多くの環境で動くコードなのかをチェックするのが難しいです。これはそれだけで多くの環境で動かすことに対する重大なリスクであるという認識です。Perlはバージョン違いの懸念はないと思いますが、全体的に用意されているライブラリが古そうという別の懸念があります。

バージョン違いという点では、Haskellのstackでは処理系も標準ライブラリも含めてインストール時にバージョンを強制できる点があって普通、パッケージマネージャでは処理系・標準ライブラリのバージョンまでは変更できず、そこで問題が出たりするのでこれも気をつけるべき点ではないかと思っています。また、Haskellで以前使われていたcabalのように他のインストール済みアプリの使っていたライブラリとバージョンが干渉して失敗するということがあったのでそこも気をつけたいですね。割とそのあたり壊れているパッケージマネージャは多そうです。

Rustは割と型がしっかりしているのでスクリプトと比べるとあからさまなエラーは埋め込みにくいです。しかし、多くの環境で使えるという観点でいうとパッケージ群の作りが荒いのか、問題が結構出そうなイメージがあります。以前PijulというVCSをインストールしようとしたとき、依存パッケージに問題がありmacじゃビルドできないという問題に当たりました。信頼性の高いパッケージに詳しい人等がいないと少しリスキーかもしれません。

Goは一応静的チェックがあり、巷の評判で何も考えなくてもマルチプラットフォームで動くようにできるとのことです。本当のところはどうなのでしょうか?

ところでRustは流行りのbetter C系言語の中で型システムがちゃんとしている例として挙げただけで、私の最近のイチオシ言語はScalaです。しかしビルドシステムのsbtと内部で使っているパッケージマネージャのIvyにはアプリをインストールする機能がなさげなのと、JVM系は色々起動がめんどくさくて各自が秘伝の必殺ランチャースクリプトを作っている状況なので除外しました。

@nya3jp
Copy link
Member

nya3jp commented Dec 22, 2016

コンパイラを揃える話

コンパイラが揃っていない問題でだいぶ苦労されたんだなということが伝わってきます。お疲れ様です……。

しかし依然としてわたしは Rime から Docker 経由でプログラムを起動するは筋が悪いと思っています。

  1. root 権限が必要であること

これはユーザが使っているマシンで root 権限を持っていないのではないかという懸念ではありません。Rime の実行を root 権限で行う必要が出てくるのではという懸念です。Rime を初めて触る作問者が、クイックスタートガイドを見て、「Rime を走らせるには sudo rime build を実行します」と書いてあったら眉をひそめますよね(何も知らない人はそのまま実行するのでしょうけど)。

Docker の起動時だけ sudo するという手もなくはないですが、わたしはユーザの明示的な許可なしに sudo するプログラムを心の底から憎んでいるので、そういう挙動を見つけたらすぐアンインストールします。そういう人はそこそこいる気がします。

総じて、root 権限が必要になる操作を Rime から実行することはセキュリティ的にもユーザートラスト的にも問題が起こりうるので、なるべく避けたいです。

  1. 実行時間の問題

コンテナの作成は軽い操作ではありません。たとえば debian:latest イメージの作成・破棄には手元で 0.6 秒前後かかります:

% time sudo docker run debian true
sudo docker run debian true  0.01s user 0.02s system 4% cpu 0.632 total

なので、たとえば Java プログラムの場合に、解答を1つのテストケースに対して実行する度にコンテナを作成・破棄するという単純なアプローチは選択肢になりえません。複数のプログラム実行を一回のコンテナ実行にまとめる必要があり、複雑度がぐっと増すだろうという懸念があります。

対案

さてここで元の話に戻りますが、コンパイラのバージョンを揃えたいのであれば、Rime から Docker を実行するのではなくて Docker から Rime を実行すればよいのではないですか?
たとえば、Rime がインストールされた Docker イメージ rime:base と、ICPC 向けのコンパイラを追加でインストールした標準イメージ rime:icpc-std を作成しておきます。ユーザは Rime プロジェクトが存在するディレクトリで次のコマンドを打つことで、統一されたコンパイラでのビルドが可能です:

$ sudo docker run -v $PWD:/workspace rime:icpc-std rime build

このアプローチだと上記二点の問題は解決されます。

@nya3jp
Copy link
Member

nya3jp commented Dec 22, 2016

ああ、ちょっと細かい点ですが訂正します。

わたしも話を聞いた当初は Rime から Docker を起動する案はありかなと思ったのですが、上記のような懸念を考えたあとだと、Docker から Rime を起動するアプローチの方が問題の解き方として正しいないう考えに至りました。

ちなみにこの方法の場合、Rime 自体のバージョン違いさえ解決できますね。

@nya3jp
Copy link
Member

nya3jp commented Dec 22, 2016

さらに追記ですが、Rime を Docker イメージとして提供することにすればバイナリ配布の問題も解決できますね。ユーザビリティの観点からのプログラミング言語の制約はこれで無くなったのでは?

@nya3jp
Copy link
Member

nya3jp commented Dec 22, 2016

ビルドシステムの話

おそらく誤解があると思うのですが、CMake や gyp は最終的に Makefile など他のビルドシステムの設定ファイルを書き出し、ビルド自体は make などに任せる、メタビルドシステムです。
わたしは、今のような特定用途向けビルドシステムのままにするにせよ、まかり間違って汎用ビルドシステムを目指すことにするにせよ、Rime に存在する長大なビルドスケジューリングシステムの部分は削除して、メタビルドシステムのアプローチをとって実際のビルドは make などに任せたほうがよいのかなあと思っています。

なので、

ビルドシステムに相当する部分がrimeのコードの中で大きな割合を占めているのは事実だと思いますが、rimeの本質的な部分はその外側の薄皮部分にある問題準備用のカスタムターゲットとカスタムタスクの集合ではないでしょうか。より詳しく言うと、カスタムターゲットとは問題・解答・テストセット・解答コード・入力生成器・入力検証器・出力検証器・…といったものであり、カスタムタスクとはテストケースビルド・解答ビルド・解答テスト・データデプロイといったものです。現行のrimeもそんな感じのアーキテクチャになっていたと思います。
可能であれば、汎用のビルド部分には注力せず、CMake, gyp等をラップして使い、その上に必要な機能をターゲット・タスクとして載せることがrimeの目指すべき方向性なのではないかと思います。

この辺の山口さんの意見とは一致していると思います(違ったらすみません)。

わたしが「ほとんど汎用のビルドシステムを作らないといけないのでは」と言っていたのは、リアクティブ、部分点、ユニットテストのようないろいろなロジックを自由にユーザに定義できるようにさせられるようにすると、ほとんど Make/CMake/... 並の自由度がコンフィグファイルに必要になってくるのでは、という懸念でした。まあ、なので、たとえばリアクティブのようなユースケースをサポートするときも、そのコード自体は Rime のバイナリの中に組み込んでしまうことにするのでしょうね。

@hiroshi-cl
Copy link
Member Author

root 権限が必要であること

おっしゃっている意味がわかりました。すっかり忘れていましたが、普通にlinuxのディストリだと初期状態ではログインするユーザーにはdockerを実行する権限がなかったんでしたね。各人にレクしてsudoなしで済むように適切に設定させるのはとても骨が折れそうです。

実行時間の問題

0.6sは耐えられないほど遅いかというと、そもそもJVMの起動時間が0.2~0.3sはかかることと比較して桁が違うわけではないので議論の余地があると思います。とはいえモッサリが体感できるレベルなのでcontainer側にrime実行サーバを用意するといったことが必要になりシステムがめんどくさくなるというのはその通りだと思います。

対案について

下のコマンドですが、以前はmacでboot2docker+virtualboxのようにremoteでdockerを動かす環境だと動作が怪しかったので考慮に入れていませんでした。今は巷の噂によると今はちゃんとローカルのファイルをmountできるらしいですし、確認しておきたいです。

docker run -v $PWD:/workspace rime:icpc-std rime build

システムはできるだけ単純な方がいいですし、rimeから直接dockerを弄る必要はなさそうですね

ビルドシステムの話

概ねそのとおりだと思います。ただ、柔軟性を高めるために自由度を高くするという方向性は、作った人が片手で数えられるくらいしかいない今のプラグインシステムと似た過ちの雰囲気を感じました。

@hiroshi-cl
Copy link
Member Author

  • とりま現状の rime-plus 入り docker image を作る
  • 多分 Go を採用するのが開発者募集とか静的チェックとかマルチプラットフォームとか枯れてるとかの諸々のバランスとかでよさそう

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

2 participants