RuboSensei Real-time Analysis Improved Learning Experience 五十嵐邦明 / igaiga, Shibuya.rb 2023/04/27
RuboSenseiReal-time Analysis Improved Learning Experience五十嵐邦明 / igaiga, Shibuya.rb 2023/04/27今日のまとめRubyを教えてくれるRuboSenseiをつくっているTypeProfを型推論ライブラリとしてつかいたいRubyKaigi 著者スタンプラリー "Rubyist Book Author" のお知らせ新刊「RubyとRailsの学習ガイド2023」本日発売月1〜4日でお仕事募集中
View Slide
RuboSenseiデモhttps://github.com/igaiga/rubocop-sensei
エディタ上で 一般的に受けられる支援コードハイライトコード補完GitHub Copilotコード生成Copilot chat 後述Copilot for docs
RuboSensei をつくりたいコードを書いているときに機械からアドバイスを受けたい人間に聞くよりも早く気軽に一般的な学ぶ方法書籍、Web、ChatGPT、動画コードを書いているタイミングで教えて欲しいRuboSenseiで目指すところコードを読むとき: リアルタイムに解説を表示して欲しいコードを書くとき: より良い書き方があったら教えてほしい
RuboSensei アーキテクチャRuboCop カスタムCopとして実装RuboCopエコシステムをつかうとVSCode上でリアルタイムにアドバイス可能RuboCopVSCode Ruby Light plugin https://marketplace.visualstudio.com/items?itemName=r7kamura.vscode-ruby-lightVSCode上でリアルタイムにRuboCop結果を表示静的解析 構文解析 Parser gem (RuboCop標準)静的解析 型推論 TypeProf
RuboCop カスタムCopとの性質の違い対象者の習熟度の違いRuboCop対象者: Rubyに習熟している学習者に対してはレベルを超えたアドバイスになることもRubyKaigi2022 "The Better RuboCop World to enjoy Ruby" でも問題提起RuboSensei対象者: Rubyにはまだ詳しくない学習者学習者が理解しやすいアドバイスを出力修正点以外でコードの理解を助けるアドバイスも出力CopのON/OFFRuboCop: コード品質向上を目的として永続的に有効化して運用RuboSensei: 学んで理解したアドバイスは無効化したい
RuboSenseiで実装したカスタムCopfoo.bar{|x| x.baz }は foo.bar(&:baz)で置き換えできるかもfoo.bar(&:baz)は foo.bar{|x| x.baz }と同じこの eachメソッドは mapメソッドで置き換えできるかもelsifは caseで書き換え可能unless elseはやめて ifで置き換えよう
RuboCop カスタムCopの実装方法概要Railsの練習帳 「RuboCop カスタムCopのつくり方」https://zenn.dev/igaiga/books/rails-practice-note/viewer/rails_rubocop_custom_cop
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 < BaseMSG = "このブロックはmethod(&:method)で置き換えられるかもしれません。"def on_block(node) #パース結果ASTにブロックなnodeが出たときif ... # ASTを判定するadd_offense(node) # RuboCopにメッセージを出させるendendendend
作戦["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であることを指示endend["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 * 2end #=> [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_srb_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 objectoptions = { 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 somethingsif expected_type.match(/(.+)\[.+\]/).captures.first == "Array"add_offense(node) #アドバイス対象ノードとしてRuboCopへ追加endTypeProfの分析結果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.htmlRubyコードの型推論を第一機能、Language ServerによるIDEサポートを第二機能として提供してきた今年はこれを逆にして、IDEを第一のターゲットにしようとしています本講演では、TypeProfの新しい設計とそのプロトタイプを紹介する予定
GitHub Copilothttps://docs.github.com/ja/copilot/getting-started-with-github-copilotコード生成Copilot chatVSCode上でチャットで聞きながらコードを書けるWaitlist に並んでつかうhttps://github.com/github-copilot/chat_waitlist_signup/Copilot for docsGitHub上のドキュメントから良い感じに答えてくれる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 をつかった対話型支援システムもつくってみたい
お仕事募集中!!【募集】月1〜4日程リモートお仕事(技術顧問、育成、実装ほか)探してます!レガシー改善実装、Railsバージョンアップ実装コード健康診断ペアプロ屋(エンジニアや他職種(QA, CS他)の方と)パーフェクトRailsやRailsの練習帳ほか読書会詳細: https://garnettech373.com/services
Rubyist Book AuthorRubyKaigi2023会場で著者訳者を探すスタンプラリー探索先著者訳者と新刊:鳥井 雪「ユウと魔法のプログラミング・ノート」江森 真由美, やだ けいこ, 小林 智恵「はじめてつくるWebアプリケーション 〜Ruby on Railsでプログラミングへの第一歩を踏み出そう」Jeremy Evans(著)、角谷 信太郎(訳)「研鑽Rubyプログラミング ―実践的なコードのための原則とトレードオフ」五十嵐 邦明「RubyとRailsの学習ガイド2023」【募集】著書訳書をお持ちでスタンプラリー探索対象で参加してくださる方!
Rubyist Book Author開催意図:話しかけられると著者たちが嬉しい!著者訳者から本を直接買える!サインももらえる!(きっと)参加方法:著者訳者を探してスタンプ台紙をもらう著者訳者と話す、本を買っても良い他の著者訳者を探してスタンプをもらう著者訳者と話す、本を買っても良い※内容は変更されることもあります
RubyとRailsの学習ガイド2023https://igaigarb.booth.pm/items/4706929BOOTHにて本日より販売開始RubyKaigi2023特別企画PDF版をお買い上げの方へノベルティとして物理書籍版をプレゼントRubyKaigi会場で私を探して購入履歴をご提示ください