Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Haskell で作る競技型イベントの裏側

252 views

Published on

https://github.com/matsubara0507/git-plantation の紹介

Published in: Engineering
  • Get Paid $25 per hour to watch YouTube videos ★★★ http://t.cn/AieXipTS
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • How Do Social Media Jobs Pay $35 Per Hour? ♥♥♥ http://ishbv.com/socialpaid/pdf
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

Haskell で作る競技型イベントの裏側

  1. 1. Haskell で作る 競技型イベントの裏側 @matsubara0507
  2. 2. ⽬次 1. 競技型イベント: mixi git challenge 2. Haskellで書き換える 3. 課題vs. Haskell
  3. 3. 競技型イベント: mixi git challenge
  4. 4. mixi git challenge git に絞った競技プログラミングコンテストのようなもの ⼆⼈で1チーム(1回12チームほど) 問題としてリポジトリが各チームに15問ぐらい⽤意 正しいコミットをプッシュできたら正解 正誤判定はスコアサーバーによって⾃動で⾏われる 正解すると難易度に応じて点数が付与される 制限時間内(約3時間)に最も点数を獲得したチームが勝利 2019年10⽉26⽇に第12回を開催(第1回が2015/11/15) 問題は使いまわしている(作問が⼀番⼤変)
  5. 5. 問題例: is-order-an-adding いくつかの簡単な問題は公開されています: mixi-git-challenge/git-challenge-is-order-an-adding # 問題: なぜか新しいファイルを追加できないリポジトリ ある顧客情報を git のリポジトリで管理しています。 新しいリストのファイル users4.csv を追加することになったのですが、 なぜか新しいファイルをリポジトリに追加できません。 追加したいファイルをどうにかしてリポジトリに追加してください。 追加できない原因を解消してもしなくても構いません。 [ファイルのリンク] ## 正答条件 - master ブランチに users4.csv をコミットして origin に push してください - 余計なファイルを含んではいけません
  6. 6. 旧スコアサーバーの構成 GitHub Webhook から採点サーバーへ(Ruby/Sinatra) 採点サーバーからJenkins の採点スクリプトを動かす TypeScript によるスコアボード(on Heroku)
  7. 7. 旧スコアサーバーの課題 「メンテナンスができない」(は⾔い過ぎ) Jenkins plugin が古すぎ ゼロから作って動くかどうか スコアボード周りを作った⼈はいない 改善したい要望があるが、これでは重い腰が上がらない
  8. 8. だったら
  9. 9. 作り直せばいいじゃない
  10. 10. Haskell で書き換える
  11. 11. 新スコアサーバーの構成 GitHub Webhook から採点サーバーへ(Haskell/Servant) 採点サーバーから採点⽤リポジトリにプッシュし DroneCI の採点スクリプトを動かす Haskell + Elm によるスコアボード DroneCI は Go 製 CI/CD プラットフォーム OSS
  12. 12. 新スコアサーバーの構成 App を介してプッシュし直してるのは以下のため .drone.yml はリポジトリに置く必要がある CI スクリプトを参加者に⾒せたくない
  13. 13. 新スコアサーバーの構成 デプロイ⽅法は雑に 設定ファイルをインスタンスにクローンして docker-compose で⼀気に⽴ち上げてる バラバラのインスタンスでもきっと動くはずだけど楽なので
  14. 14. 新スコアサーバーの構成 スコアボードはこんな感じ: ネタバレ防⽌のため問題名は伏せてます
  15. 15. git-plantaion OSS にしました(Haskell 2500⾏ぐらい) 複数のアプリケーションを1つのDocker Image にしてある: app : GitHub Webhook やスコアボードを提供したり store : ユーザーデータのキャッシュサーバー tool : 回答リポジトリなどを作るツール群 slack : slack ⽤のAPI を提供 git-plantation Createby@matsubara0507 mixigitchallengeの新しい採点システム 11STARS 2FORKS
  16. 16. 利⽤パッケージ Haskell にはパッケージが盛りだくさん rio : Alt. Prelude extensible : 拡張可能レコードなど mix.hs : rio + extensible な⾃作フレームワーク shelly : thread safe にシェルコマンドを実⾏できる servant : 型レベルWeb DSL elmap.hs : Haskell の定義をElm に出⼒ github, drone : Web API クライアント req, wreq : HTTP クライアント
  17. 17. 課題vs. Haskell
  18. 18. Drone とHaskell Drone CI にはAPI が提供されている Haskell から呼び出したいのでAPI クライアントを作った: 細かいドキュメントがないので公式のGo 製API クライアント を読んで模倣実装した drone-haskell Createby@matsubara0507 HaskellclientfortheDroneAPI 2STARS 1FORKS
  19. 19. Elm とHaskell Haskell Servant はServant の定義などから、いろんな⾔語へ のAPI クライアントを⾃動⽣成することができる: servant-ruby servant-py servant-js servant-csharp servant-kotlin servant-elm などなど これらが現在も動作するかどうかはわかりません
  20. 20. Elm とHaskell servant-elm + elm-bridge を使う extensible はうまく動作しないので対応したものを作った elmap.hs Createby@matsubara0507 MappingtoElmdefinitionsfromHaskell definitions. 0STARS 0FORKS
  21. 21. Elm とHaskell こういう定義から type Todo = Record '[ "id" >: Int , "title" >: String , "done" >: Bool ] instance IsElmType Todo where compileElmType = compileElmRecordTypeWith "Todo" instance IsElmDefinition Todo where compileElmDef = ETypeAlias . compileElmRecordAliasWith "Todo" type CRUD = "todos" :> Get '[JSON] [Todo] :<|> "todos" :> ReqBody '[JSON, FormUrlEncoded] Todo :> Post '[JSON] Todo
  22. 22. Elm とHaskell こういうElm コードを⽣成してくれる type alias Todo = { id : Int , title : String , done : Bool } getApiTodos : (Result Http.Error (List Todo) -> msg) -> Cmd msg getApiTodos = ... postApiTodos : Todo -> (Result Http.Error Todo -> msg) -> Cmd msg postApiTodos = ... JSON のデコーダー・エンコーダーも⼀緒に⽣成する
  23. 23. キャッシュとHaskell 初めて新システムを導⼊した時にスコアボードとDrone CI 間 の負荷がきつかった(雑に作ったせいで。。。) ということでキャッシュサーバーを間に置いた(⾃作) ブラウザ(スコアボード) <--> App <--> Store(cache) <--> Drone
  24. 24. キャッシュとHaskell 実装はServant + STM (超簡単) type Store = IntMap [Build] -- Build is target data type type API = Get '[JSON] Store :<|> Capture "problem" Int :> Patch '[JSON] NoContent server :: TVar Store -> ServerT API Plant server store = getStore :<|> putStore where getStore = liftIO $ readTVarIO store putStore pid = do findProblemWith pid $ problem -> do builds <- uniqByTeam <$> fetchBuilds problem liftIO $ atomically (modifyTVar' store $ modifyWith problem builds) pure NoContent modifyWith :: Problem -> [Build] -> Store -> Store modifyWith = ...
  25. 25. Docker とHaskell Docker を使えば簡単に実⾏ファイルを配布できるので Docker Image を作りたい Stack v1 の頃は stack image コマンドで簡単に作れた: # stack.yaml # stack --docker image container docker: repo: fpco/stack-build enable: false image: container: name: [image_name] base: fpco/ubuntu-with-libgmp しかし、Stack v2 から無くなったので⽅法を考えた
  26. 26. Docker とHaskell Haskell Stack のDocker Integration を使うことで指定したイ メージ環境でビルドできる: # stack.yaml # stack build --docker docker: # 軽量な自作イメージ(ubuntu) repo: matsubara0507/stack-build enable: false
  27. 27. Docker とHaskell さらに、こんな感じのDockerfile を使うことでイメージを簡 単に作れる FROM matsubara0507/ubuntu-for-haskell ARG local_bin_path RUN mkdir -p /root/.local/bin && mkdir -p /root/work ENV PATH /root/.local/bin:$PATH WORKDIR /root/work COPY ${local_bin_path} /root/.local/bin $ stack --local-bin-path=./bin --docker install $ docker build . --build-arg local_bin_path=./bin さらにこれで .stack-work を .dockerignore に含めれる
  28. 28. フレームワークとHaskell rio は便利だが最初の設定がめんどくさい: data App = App { appLogFunc :: !LogFunc, appName :: !Utf8Builder } instance HasLogFunc App where logFuncL = lens appLogFunc (x y -> x { appLogFunc = y }) main = runApp $ do name <- view $ to appName logInfo $ "Hello, " <> name runApp :: RIO App a -> IO a runApp inner = do logOptions' <- logOptionsHandle stderr False let logOptions = setLogUseTime True $ setLogUseLoc True logOptions' withLogFunc logOptions $ logFunc -> do let app = App { appLogFunc = logFunc, appName = "Alice" } runRIO app inner
  29. 29. フレームワークとHaskell このあたりを解決したのがtonatona extensible と継続モナドを使えばもっと簡単に書けるような気 がしたのでパッケージにした: mix.hs Createby@matsubara0507 buildingriopackageconfigurationusing contmonadwithextensible 4STARS 0FORKS
  30. 30. フレームワークとHaskell こんな感じ import Mix import Mix.Plugin.Logger as MixLogger type App = Record '[ "logger" >: MixLogger.LogFunc, "name" >: Text ] main :: IO () main = Mix.run plugin $ do name <- asks (view #name) logInfo $ display ("Hello, " <> name) where plugin :: Plugin () IO App plugin = hsequence $ #logger <@=> MixLogger.buildPlugin logOpts <: #name <@=> pure "Hoge" <: nil logOpts = #handle @= stdout <: #verbose @= True <: nil
  31. 31. おまけ: 設定とDhall Haskell Day 2018 のDhall の発表を聞いて⾯⽩そうだったの で全ての設定をDhall に置き換えた
  32. 32. まとめ mixi git challenge はHaskell 製OSS で動いてます ⾜りないものはなんでも⾃作してます(楽しい) 競技型イベントはサンドボックスとしては最良です
  33. 33. おしまい

×