Share
Embed
Shibuya.rb-2023-04-27-igaiga
RuboSensei
Real-time Analysis Improved Learning Experience
五十嵐邦明 / igaiga, Shibuya.rb 2023/04/27
Kuniaki IGARASHI
April 27, 2023
Transcript
26 slides
-
RuboSensei Real-time Analysis Improved Learning Experience 五十嵐邦明 / igaiga, Shibuya.rb
2023/04/27 今日のまとめ Rubyを教えてくれるRuboSenseiをつくっている TypeProfを型推論ライブラリとしてつかいたい RubyKaigi 著者スタンプラリー "Rubyist Book Author" のお知らせ 新刊「RubyとRailsの学習ガイド2023」本日発売 月1〜4日でお仕事募集中 -
RuboSensei アーキテクチャ RuboCop カスタムCopとして実装 RuboCopエコシステムをつかうとVSCode上でリアルタイムにアドバイス可能 RuboCop VSCode Ruby Light plugin
https://marketplace.visualstudio.com/items? itemName=r7kamura.vscode-ruby-light VSCode上でリアルタイムにRuboCop結果を表示 静的解析 構文解析 Parser gem (RuboCop標準) 静的解析 型推論 TypeProf -
RuboCop カスタムCopとの性質の違い 対象者の習熟度の違い RuboCop 対象者: Rubyに習熟している 学習者に対してはレベルを超えたアドバイスになることも RubyKaigi2022 "The Better
RuboCop World to enjoy Ruby" でも問題提起 RuboSensei 対象者: Rubyにはまだ詳しくない学習者 学習者が理解しやすいアドバイスを出力 修正点以外でコードの理解を助けるアドバイスも出力 CopのON/OFF RuboCop: コード品質向上を目的として永続的に有効化して運用 RuboSensei: 学んで理解したアドバイスは無効化したい -
RuboSenseiで実装したカスタムCop foo.bar{|x| x.baz } は foo.bar(&:baz) で置き換えできるかも foo.bar(&:baz) は foo.bar{|x|
x.baz } と同じ この each メソッドは map メソッドで置き換えできるかも elsif は case で書き換え可能 unless else はやめて if で置き換えよう -
RuboCop Copの基本形 例: ["a","b"].map{|x| x.upcase} ["a","b"].map(&:upcase) 分析対象: method1{ |x| x.method2
} アドバイス: method1(&:method2) で置換できる module RuboCop::Cop::Lecture # good & bad comment ( 省略) class PreferSymbolToProc < Base MSG = " このブロックはmethod(&:method) で置き換えられるかもしれません。" def on_block(node) # パース結果AST にブロックなnode が出たとき if ... # AST を判定する add_offense(node) # RuboCop にメッセージを出させる end end end end -
作戦 ["a","b"].map{|x| x.upcase} ["a","b"].map(&:upcase) 上記の書き換えが可能な条件をASTから判定する作戦を考える 誤検知がたまに出ても良いことにして、実利を優先する指針 判定条件 ブロック中に1つの式(メソッド)しか書いていない その唯一のメソッドに引数が渡されていない アドバイスを含んだRuboCop
Copとしての全コード https://github.com/igaiga/rubocop- sensei/blob/v0.1.4/lib/rubocop/cop/lecture/prefer_symbol_to_proc.rb -
AST # 対象コード: array.map{|x| x.upcase} def on_block(node) # node #=>
# s(:block, # s(:send, # s(:send, nil, :array), :map), # ここは判定に不要 # s(:args, # s(:arg, :x)), # ここは判定に不要 # s(:send, # ブロック中の式が1 つだけ判定 # s(:lvar, :x), :upcase)) # 引数なし判定 「ブロック中に1メソッドしか書いていない」 ブロック中に2メソッド以上書かれているときにはASTにbeginノードが入る beginノードがないことで判定 メソッドであることは node.send_type? で判定 引数有無は node.arguments? で判定 -
判定コード # 対象コード: array.map{|x| x.upcase} def on_block(node) # node #=>
# s(:block, # s(:send, # s(:send, nil, :array), :map), # ここは判定に不要 # s(:args, # s(:arg, :x)), # ここは判定に不要 # s(:send, # ブロック中の式が1 つだけのとき、:begin ノードが親に入らない # s(:lvar, :x), :upcase)) # 引数なし target_nodes = node.child_nodes - [node.send_node] # map メソッドnode は除く send_type_nodes = target_nodes.select(&:send_type?) # :begin node が入らず、直下にあること if send_type_nodes.count == 1 && !send_type_nodes.last.arguments? # 引数なし add_offense(node) # アドバイス可能なnode であることを指示 end end ["a","b"].map{|x| x.upcase} ["a","b"].map(&:upcase) を表示できる -
ASTとTypeProf型推論を組み合わせて分析 Parserで解析したASTのノードの型情報も欲しいケース 例:「このeachメソッドはmapメソッドで置き換えできるかも」 result = [] [1,2,3].each do |x| result
<< x * 2 end #=> [2,4,6] 「レシーバresultの型がArrayであること」を知りたい 型の情報をTypeProfで推論する TypeProfへRubyコード片を渡して分析したい -
TypeProfとは Ruby標準添付の型推論ライブラリ ターミナルでRubyコードファイルを入力にRBSファイルを出力する $ typeprof foo.rb -o foo.rbs おそらくこれが主用途 今回は入出力をファイルではなくRubyオブジェクトにしたい
謝辞: TypeProfのつかい方をmameさんにサポートいただきました もしかすると、別解としてTypeProfをLSPとしてつかえば楽なのかも? -
TypeProfへRubyオブジェクトを入出力して分析したい 出力: StringIOオブジェクト(標準入出力とStringを変換できる) 入力: ダミーファイル名文字列と対象Rubyコード文字列の配列の配列 [["target.rb", target_ruby_code_string]] 加えて、入力するRubyコードに細工 を加える必要がある 型情報を得たい変数へ代入しているコードを加える
ブロックノードの親ノードを渡す 必要に応じてさらに上部を含んでも良さそう TypeProfは対象変数の出力がないと解析対象としない 「p 変数」コードを追加して、TypeProfが解析可能な形のコードを作成 -
TypeProfへRubyオブジェクトを入出力して分析するコード(入力編) ## レシーバの型がArray であるかどうかをTypeProf で調査 rb_text = node.parent.source # レシーバの定義元を含んだコード片を取得
receiver_variable_name = node.child_nodes.last.receiver.node_parts.first.to_s rb_text += "\n" + "p #{receiver_variable_name}" # TypeProf 分析のため p レシーバ を追加 target_line_number = rb_text.lines.count # TypeProf 出力結果の解析に行番号が必要 rb_files = [["target.rb", rb_text]] # 入力 rbs_files = [] # RBS はつかわない output = StringIO.new(String.new("")) # 出力 String.new("") = mutable String object options = { show_errors: true } config = TypeProf::ConfigData.new(rb_files: rb_files, rbs_files: rbs_files, output: output, max_sec: 5, options: options) TypeProf.analyze(config) # 分析実行 RuboCop Copとしての全コード https://github.com/igaiga/rubocop- sensei/blob/v0.1.4/lib/rubocop/cop/lecture/prefer_map.rb -
TypeProfへRubyオブジェクトを入出力して分析するコード(出力編) #... TypeProf.analyze(config) # 分析 #=> "# TypeProf 0.21.4\n\n# Revealed
types\n# target.rb:6 #=> Array[Integer]\n\n# Classes\n" expected_type = output.string.match(/target.rb:#{target_line_number}\s*#=>\s*(.+)$/).captures.first #=> Array[SomeClass] or untyped or somethings if expected_type.match(/(.+)\[.+\]/).captures.first == "Array" add_offense(node) # アドバイス対象ノードとしてRuboCop へ追加 end TypeProfの分析結果outputはStringIOオブジェクト ターミナル出力文字列を得る # TypeProf 0.21.4\n\n... # target.rb:6 #=> Array[Integer]\n\n... 正規表現をつかって該当部分の情報を得る ASTと型情報とを結合するために入力したコードの行番号をつかう 1行に複数式があるケースなどは誤分析になるかも TypeProfで型がArrayであることがわかった! TypeProfの型推論すごい -
TypeProfでこんな機能が欲しいの提言 今回はRBS出力部分と型推論部分を別々につかいたいユースケース 入出力をRubyオブジェクトで渡せる型推論APIがあると便利 入力にRubyコードのStringオブジェクトを直接渡したい 出力をHashや分析結果クラスのオブジェクトなど扱いやすいオブジェクトで得たい 構文解析結果のASTから、各ノードの型情報が得られるととても便利 型情報を含んだASTほしい! RubyKaigi 2023 mameさん
"Revisiting TypeProf - IDE support as a primary feature" https://rubykaigi.org/2023/presentations/mametter.html Rubyコードの型推論を第一機能、Language ServerによるIDEサポートを第二機能 として提供してきた 今年はこれを逆にして、IDEを第一のターゲットにしようとしています 本講演では、TypeProfの新しい設計とそのプロトタイプを紹介する予定 -
GitHub Copilot https://docs.github.com/ja/copilot/getting-started-with-github-copilot コード生成 Copilot chat VSCode上でチャットで聞きながらコードを書ける Waitlist に並んでつかう https://github.com/github-copilot/chat_waitlist_signup/
Copilot for docs GitHub上のドキュメントから良い感じに答えてくれる Waitlist に並んでつかう https://githubnext.com/projects/copilot-for-docs -
GitHub Copilot chat vs RuboSensei の現在 GitHub Copilot chat アドバイスの正しさを判断する必要がある
なんでも聞いたら教えてくれる アドバイスに対して深掘りした再質問もできる RuboSensei 信用に足るアドバイスを出せる 教えてくれる内容を開発者が実装する必要がある -
GitHub Copilot chat vs RuboSensei の未来 GitHub Copilot chat が正しいアドバイスを常にしてくれる未来
「正しいアドバイス」とは? 正しい = 学習者の現在のコンテキストに最適 技術的な正しさ + 学習ステージのマッチ + 目的と方向のマッチ GitHub Copilot chatは唯一神がコンテキストを動的に推測する RuboSensei は多くのコンテキストに対応できる最大公約数アドバイスを静的に実装 RuboSensei は実装者(教師)によって教えることの多様性があることが強み? ChatGPT API をつかった対話型支援システムもつくってみたい -
Rubyist Book Author RubyKaigi2023会場で著者訳者を探すスタンプラリー 探索先著者訳者と新刊: 鳥井 雪 「ユウと魔法のプログラミング・ノート」 江森 真由美,
やだ けいこ, 小林 智恵 「はじめてつくるWebアプリケーション 〜Ruby on Railsでプログラミングへ の第一歩を踏み出そう」 Jeremy Evans(著)、角谷 信太郎(訳) 「研鑽Rubyプログラミング ― 実践的なコードのための原則とトレードオフ」 五十嵐 邦明 「RubyとRailsの学習ガイド2023」 【募集】著書訳書をお持ちでスタンプラリー探索対象で参加してくださる方! -
Rubyist Book Author 開催意図: 話しかけられると著者たちが嬉しい! 著者訳者から本を直接買える!サインも もらえる!(きっと) 参加方法: 著者訳者を探してスタンプ台紙をもらう 著者訳者と話す、本を買っても良い
他の著者訳者を探してスタンプをもらう 著者訳者と話す、本を買っても良い ※ 内容は変更されることもあります