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

Shibuya.rb-2023-04-27-igaiga

 Shibuya.rb-2023-04-27-igaiga

RuboSensei
Real-time Analysis Improved Learning Experience
五十嵐邦明 / igaiga, Shibuya.rb 2023/04/27

Kuniaki IGARASHI

April 27, 2023
Tweet

More Decks by Kuniaki IGARASHI

Other Decks in Technology

Transcript

  1. 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日でお仕事募集中

    View Slide

  2. RuboSenseiデモ
    https://github.com/igaiga/rubocop-sensei

    View Slide

  3. エディタ上で 一般的に受けられる支援
    コードハイライト
    コード補完
    GitHub Copilot
    コード生成
    Copilot chat 後述
    Copilot for docs

    View Slide

  4. RuboSensei をつくりたい
    コードを書いているときに機械からアドバイスを受けたい
    人間に聞くよりも早く気軽に
    一般的な学ぶ方法
    書籍、Web、ChatGPT、動画
    コードを書いているタイミングで教えて欲しい
    RuboSenseiで目指すところ
    コードを読むとき: リアルタイムに解説を表示して欲しい
    コードを書くとき: より良い書き方があったら教えてほしい

    View Slide

  5. 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

    View Slide

  6. RuboCop カスタムCopとの性質の違い
    対象者の習熟度の違い
    RuboCop
    対象者: Rubyに習熟している
    学習者に対してはレベルを超えたアドバイスになることも
    RubyKaigi2022 "The Better RuboCop World to enjoy Ruby" でも問題提起
    RuboSensei
    対象者: Rubyにはまだ詳しくない学習者
    学習者が理解しやすいアドバイスを出力
    修正点以外でコードの理解を助けるアドバイスも出力
    CopのON/OFF
    RuboCop: コード品質向上を目的として永続的に有効化して運用
    RuboSensei: 学んで理解したアドバイスは無効化したい

    View Slide

  7. 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
    で置き換えよう

    View Slide

  8. RuboCop カスタムCopの実装方法概要
    Railsの練習帳 「RuboCop カスタムCopのつくり方」
    https://zenn.dev/igaiga/books/rails-practice-note/viewer/rails_rubocop_custom_cop

    View Slide

  9. 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

    View Slide

  10. 作戦
    ["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

    View Slide

  11. 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?
    で判定

    View Slide

  12. 判定コード
    #
    対象コード: 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)
    を表示できる

    View Slide

  13. 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コード片を渡して分析したい

    View Slide

  14. TypeProfとは
    Ruby標準添付の型推論ライブラリ
    ターミナルでRubyコードファイルを入力にRBSファイルを出力する
    $ typeprof foo.rb -o foo.rbs
    おそらくこれが主用途
    今回は入出力をファイルではなくRubyオブジェクトにしたい
    謝辞: TypeProfのつかい方をmameさんにサポートいただきました
    もしかすると、別解としてTypeProfをLSPとしてつかえば楽なのかも?

    View Slide

  15. TypeProfへRubyオブジェクトを入出力して分析したい
    出力: StringIOオブジェクト(標準入出力とStringを変換できる)
    入力: ダミーファイル名文字列と対象Rubyコード文字列の配列の配列
    [["target.rb", target_ruby_code_string]]
    加えて、入力するRubyコードに細工 を加える必要がある
    型情報を得たい変数へ代入しているコードを加える
    ブロックノードの親ノードを渡す
    必要に応じてさらに上部を含んでも良さそう
    TypeProfは対象変数の出力がないと解析対象としない
    「p 変数」コードを追加して、TypeProfが解析可能な形のコードを作成

    View Slide

  16. 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

    View Slide

  17. 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の型推論すごい

    View Slide

  18. 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の新しい設計とそのプロトタイプを紹介する予定

    View Slide

  19. 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

    View Slide

  20. GitHub Copilot chat vs RuboSensei の現在
    GitHub Copilot chat
    アドバイスの正しさを判断する必要がある
    なんでも聞いたら教えてくれる
    アドバイスに対して深掘りした再質問もできる
    RuboSensei
    信用に足るアドバイスを出せる
    教えてくれる内容を開発者が実装する必要がある

    View Slide

  21. GitHub Copilot chat vs RuboSensei の未来
    GitHub Copilot chat が正しいアドバイスを常にしてくれる未来
    「正しいアドバイス」とは?
    正しい = 学習者の現在のコンテキストに最適
    技術的な正しさ + 学習ステージのマッチ + 目的と方向のマッチ
    GitHub Copilot chatは唯一神がコンテキストを動的に推測する
    RuboSensei は多くのコンテキストに対応できる最大公約数アドバイスを静的に実装
    RuboSensei は実装者(教師)によって教えることの多様性があることが強み?
    ChatGPT API をつかった対話型支援システムもつくってみたい

    View Slide

  22. お仕事募集中!!
    【募集】月1〜4日程リモートお仕事(技術顧問、育成、実装ほか)探してます!
    レガシー改善実装、Railsバージョンアップ実装
    コード健康診断
    ペアプロ屋(エンジニアや他職種(QA, CS他)の方と)
    パーフェクトRailsやRailsの練習帳ほか読書会
    詳細: https://garnettech373.com/services

    View Slide

  23. View Slide

  24. Rubyist Book Author
    RubyKaigi2023会場で著者訳者を探すスタンプラリー
    探索先著者訳者と新刊:
    鳥井 雪
    「ユウと魔法のプログラミング・ノート」
    江森 真由美, やだ けいこ, 小林 智恵
    「はじめてつくるWebアプリケーション 〜Ruby on Railsでプログラミングへ
    の第一歩を踏み出そう」
    Jeremy Evans(著)、角谷 信太郎(訳)
    「研鑽Rubyプログラミング ―
    実践的なコードのための原則とトレードオフ」
    五十嵐 邦明
    「RubyとRailsの学習ガイド2023」
    【募集】著書訳書をお持ちでスタンプラリー探索対象で参加してくださる方!

    View Slide

  25. Rubyist Book Author
    開催意図:
    話しかけられると著者たちが嬉しい!
    著者訳者から本を直接買える!サインも
    もらえる!(きっと)
    参加方法:
    著者訳者を探してスタンプ台紙をもらう
    著者訳者と話す、本を買っても良い
    他の著者訳者を探してスタンプをもらう
    著者訳者と話す、本を買っても良い

    内容は変更されることもあります

    View Slide

  26. RubyとRailsの学習ガイド2023
    https://igaigarb.booth.pm/items/4706929
    BOOTHにて本日より販売開始
    RubyKaigi2023特別企画
    PDF版をお買い上げの方へノベルティと
    して物理書籍版をプレゼント
    RubyKaigi会場で私を探して購入履歴を
    ご提示ください

    View Slide