LoginSignup
12

More than 5 years have passed since last update.

RubyのAST入門 #omotesandorb

Last updated at Posted at 2018-05-10
1 / 28

表参道.rb #34
https://omotesandorb.connpass.com/event/86444/

の発表資料。


自己紹介

名前: sinsoku
会社: 株式会社DMM.comラボ(🔞🙅)
副業: 株式会社grooves
github: sinsoku
twitter: @sinsoku_listy


話すこと

  1. ASTの概要
  2. 実務で役立つAST
  3. ASTの限界
  4. そして型推論の夢を見る...

AST(抽象構文木)の概要


ASTを知っている人?✋


ASTの概要

Rubyはコードを下記の順で解析され、実行されます。

  1. 字句解析
  2. 構文解析
  3. 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は意外と簡単なのでみんな触ろう!
  • 型に興味ある人はぜひ話しましょう!

ご清聴ありがとうございました。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12