表参道.rb #34
https://omotesandorb.connpass.com/event/86444/
の発表資料。
自己紹介
名前: sinsoku
会社: 株式会社DMM.comラボ(🔞🙅)
副業: 株式会社grooves
github: sinsoku
twitter: @sinsoku_listy
話すこと
- ASTの概要
- 実務で役立つAST
- ASTの限界
- そして型推論の夢を見る...
AST(抽象構文木)の概要
ASTを知っている人?✋
ASTの概要
Rubyはコードを下記の順で解析され、実行されます。
- 字句解析
- 構文解析
- YARV(Yet Another Ruby VM) 上で実行
- コードをiseqにして動かす
- iseq(InstructionSequence)
字句解析
Rubyのコードを単語に分ける。
Ripper.lex("a = 1 + 1").each { |t| p t }
# [[1, 0], :on_ident, "a", #<Ripper::Lexer::State: EXPR_CMDARG>]
# [[1, 1], :on_sp, " ", #<Ripper::Lexer::State: EXPR_CMDARG>]
# [[1, 2], :on_op, "=", #<Ripper::Lexer::State: EXPR_BEG>]
# [[1, 3], :on_sp, " ", #<Ripper::Lexer::State: EXPR_BEG>]
# [[1, 4], :on_int, "1", #<Ripper::Lexer::State: EXPR_END>]
# [[1, 5], :on_sp, " ", #<Ripper::Lexer::State: EXPR_END>]
# [[1, 6], :on_op, "+", #<Ripper::Lexer::State: EXPR_BEG>]
# [[1, 7], :on_sp, " ", #<Ripper::Lexer::State: EXPR_BEG>]
# [[1, 8], :on_int, "1", #<Ripper::Lexer::State: EXPR_END>]
構文解析
単語からRuby構文を表現するAST(抽象構文木)を作る。
Ripper.sexp("a = 1 + 1")
# [:program,
# [[:assign,
# [:var_field, [:@ident, "a", [1, 0]]],
# [:binary, [:@int, "1", [1, 4]], :+, [:@int, "1", [1, 8]]]]]]
YARV上で実行
ASTをiseqという命令列に変換し、YARV上で実行する。
詳細を知りたい人は下記の本を読むと良いです。
実務で役立つAST
RuboCop の拡張がオススメ!
💀404 NotFound
資料が間に合わなかった。。。
ASTの限界
👮 RuboCop DynamicFindBy
class User < ActiveRecord::Base
end
User.find_by_name("foo") # <= 1 offence
👮 false positive
RuboCop さんは下記のコードを誤検知します。
class Foo
def self.find_by_name(name); end
end
Foo.find_by_name("bar")
なぜ誤検知するのか?
RuboCop はメソッド名のみをチェックしていて、
ActiveRecord::Base の子孫かどうかはチェックしていない。
Rubyには型がないため、どうしようも無い...。
そして型推論の夢を見る
リテラルの型は分かる
num = 1
#=> Integer型
array = []
#=> Array型
newの戻り値の型も分かる
class User
end
user = User.new
#=> User型
newの戻り値の型も分かる
Ripper.sexp("user = User.new")
# [:program,
# [[:assign,
# [:var_field, [:@ident, "user", [1, 0]]],
# [:call,
# [:var_ref, [:@const, "User", [1, 7]]],
# [:@period, ".", [1, 11]],
# [:@ident, "new", [1, 12]]]]]]
(Railsの)schem.rbも使えそう
create_table "users" do |t|
t.string "email", default: "", null: false
t.integer "age", null: false
end
ASTで推論できるかも?
上書きされるnew
class User
def self.new
Admin.new
end
end
user = User.new
#=> Admin型
引数で変わる型
def pick(*column_names)
limit(1).pluck(*column_names).first
end
User.pick(:name)
#=> "David Heinemeier Hansson"
User.pick(:name, :admin)
#=> ["David Heinemeier Hansson", true]
世知辛いのじゃ...
- Ruby の世界は厳しい
- ただ、7割くらい推論できる気がする
まとめ
- ASTは意外と簡単なのでみんな触ろう!
- 型に興味ある人はぜひ話しましょう!
ご清聴ありがとうございました。