Ruby
概要
Rubyはオブジェクト指向のProgramming Languageである。 WebフレームワークのRailsが有名で、採用されるケースはRailsを使うためが多い。
コンパイル不要なスクリプト言語、動的型付け、WEBに使用される、といった点でPythonと競合している。
Memo
countにブロックを渡す
countにブロックを渡して配列の数を調べられる。 ↓二行は同じ意味。
expect(item_type_pool.types.select { |t| t.category == :canon }.length).to be > 10 expect(item_type_pool.types.count { |t| t.category == :canon }).to be > 10
継承関係を辿る
true.class.ancestors
オブジェクトのメソッド一覧を見る
true.public_methods
falseを渡すと祖先のメソッドを表示しない。
true.public_methods(false) =# => [:===, :^, :inspect, :to_s, :&, :|]
group_by
Enumerable#group_by
ブロックを評価した結果をキー、対応する要素の配列を値とするハッシュを返す。
QueryMethodの where
で取った値をハッシュにして、後で使いまわせる。N+1問題の回避に使える。QueryMethodぽい名前だが無関係。
index_by
viewで何かモデルに関することをループさせないといけないときに役立つ。モデルを一度にハッシュとして取ることで、パフォーマンスを改善できる。
インスタンスメソッドを調査する
String.instance_methods(false).sort
false
によってクラスの継承メソッドを表示しないため、クラス単体を調べるのに役立つ。
トップレベルで実行できる理由
クラスがなくトップレベルで定義されたメソッドのレシーバーは Object
クラス。クラスの中にないトップレベルメソッドでさまざまなことが行えるのは、 Object
のおかげ。 ruby -e 'p Kernel.private_instance_methods.sort'
でチェックできる。
puts
がレシーバーなしで呼び出せるのは、Object
クラスがputs
のあるKernel
クラスをincludeしているから。.to_d
- BigDecimalに変換する。index
- 配列を検索して添字を返す。
何のメソッドがわからないとき
- Emacsだと
robe-doc
がとても便利。すでにあるローカルにあるドキュメントを活用するべき。
when句
https://stackoverflow.com/questions/3908380/ruby-class-types-and-case-statements/3908411
case item when MyClass ... when Array ... when String ...
is really
if MyClass === item ... elsif Array === item ... elsif String === item ...
===
は内部的に is_a?
を使っている。
if item.is_a?(MyClass) ... elsif item.is_a?(Array) ... elsif item.is_a?(String) ...
をcaseに書き換えるには一番上の書き方でよい。たぶん。
singletonをクリーンにテストする
singletonをそのまま使うと状況依存のテストになるため、毎回newする必要がある。
https://stackoverflow.com/questions/1909181/how-to-test-a-singleton-class
def self.instance @instance ||= new end private_class_method :new
So you can bypass the memoization altogether by calling the private method new using send
let(:instance) { GlobalClass.send(:new) }
A nice benefit of this way is that no global state is modified as a result of your tests running.
Probably a better way, from this answer:
let(:instance) { Class.new(GlobalClass).instance }
評価結果アノテーションを付与するxmpfilter
便利ツールを集めた https://github.com/rcodetools/rcodetools というgemがある。 そのなかにインラインで実行した結果を表示するスクリプトがある。 Emacs用のコードもある。https://github.com/rcodetools/rcodetools/blob/master/misc/rcodetools.el rubykitch氏作成。
1.to_s # => "1"
というように、irbのように挿入してくれる。とても便利。
bundle installの並列数
bundle install --jobs 4
などとして並列処理数を指定できる。
このマックスの数の調べ方。
getconf _NPROCESSORS_ONLN
なので、マシンごとで最速の設定で実行するためには。
bundle install --jobs `getconf _NPROCESSORS_ONLN`
とする。
https://stackoverflow.com/questions/39163758/bundle-how-many-parallel-jobs
transform_values
map { } .to_h
はtransform_valuesで書き直せる。
h = { a: 1, b: 2 } h.map { |k, v| [k, v.to_s] }.to_h
h = { a: 1, b: 2 } h.transform_values(&:to_s)
日付計算でのRational
日付計算でDate同士を計算するときがある。 そのとき返ってくる値を表示すると-1/1みたいに表示される。 これはRational(有理数)オブジェクトである。 .to_iで整数に変換できる。
マッチした部分文字列の配列を返すscan
"foobar".scan(/../)
[“fo”, “ob”, “ar”]
"1 2 3".scan(/\d+/)
[“1”, “2”, “3”]
"1, 2, 3".scan(/\d+/)
[“1”, “2”, “3”]
メモリ使用量を調べる
require 'objspace' puts "#{ObjectSpace.memsize_of_all / (1000.0 * 1000.0)} MB"
irbでgemを読み込む
require 'rspec' include RSpec::Matchers include ActionView::Helpers::OutputSafetyHelper
クラスの読み込み順
親(抽象)クラスは、子(具体)クラスよりも先に読み込む必要がある。 普通に開発していると1つのファイルに入れることはないので気づきにくい、はまりやすい。
↓はエラーになる。
class A < B end class B end
親クラスから子クラスの定数にアクセス
class Abstruct def print_child_constant self.class::NAME end end class A < Abstruct NAME = 'AA' end class B < Abstruct NAME = 'BB' end p A.new.print_child_constant # AA
配列をマージ/結合
merge
メソッドはHashクラスのメソッドであり、配列では使えない。
単純な結合。
['a', 'b'] + ['a', 'b']
[“a”, “b”, “a”, “b”]
マージ(=かぶってたら削除)。
['a', 'b'] | ['a', 'b']
[“a”, “b”]
uniqでも同じ。
(['a', 'b'] + ['a', 'b']).uniq
[“a”, “b”]
Struct(構造体クラス)
structは簡易的なclassのようなもの。 データをまとめるのに使う。
user = Struct.new(:name, :age) user.new('taro', 15)
#<struct name=“taro”, age=15>
thor
thorはコマンドを作るgem。 同じようなライブラリにrakeがあるが、rakeは引数を渡す方法が特殊なのでthorが好まれる。
module Gemat class Cli < Thor class_options input: :string, output: :string, columns: :array, all: :boolean # メソッド共通のオプション desc 'csv', 'csv command description' def csv end desc md, 'md command description' def md end no_tasks do def command(options, method_name) end end end end
$ gemat csv
pp
Hashが見づらいときは、 pp
を使うと綺麗に表示できる。
https://docs.ruby-lang.org/ja/latest/library/pp.html
map
mapの返り値は、ブロックの最後の値である。 だから↓みたく途中でセットしたい、というときは最後配列に入れたいものを置く必要がある。
options[:columns].map do |column| od = OutDsl.new(column) od.idx = index od # ここ end
mapは1行で書くこと多いので忘れがち。
rubygemsのcredential入手
https://rubygems.org/ であらかじめログインしておく。
curl -u {user名} https://rubygems.org/api/v1/api_key.yaml > ~/.gem/credentials; chmod 0600 ~/.gem/credentials
rake release
presense
present?
の結果がtrueのときレシーバ自身を返す。falseのときはnilを返す。
object.present? ? object : nil
object.presense
これらは等価である。
tap
処理に関わらずselfを返す。 メソッドチェーンへのデバッグに便利。
p ( 1 .. 5 ) .tap{|obj| puts obj.class} .to_a.tap{|obj| puts obj.class}
Range Array [1, 2, 3, 4, 5]
メソッドチェーンの途中で分岐として使えそう。
min_by
配列から最大/最小の値を取りたいというとき、min_byが便利。
[5, -8, 3, 9].min_by{|num| num.abs }
3
order → first と冗長に書いてしまいがち。
&: 記法
%w{ a b c }.map(&:capitalize)
- & ->
to_proc
trigger - : -> symbol
ファイルを作らずにファイルオブジェクトを作ってテストする
ファイル入力のあるプログラムがあるとする。
テストするとき、普通はファイルを作って読み込むことになる。
しかしいちいちファイルを用意するほどではない、みたいな場合もある。
そのときは StringIO
を使うと気軽に試せる。
require 'stringio' string = <<EOM aaa "aaa" EOM file1 = StringIO.new(string) file.read # => aaa\n"aaa" file2 = StringIO.new('') file.read # => ""
としておいて、あとは普通のFIleオブジェクトにするように、 StringIO
オブジェクトに対して各種操作ができる。
Mastering Ruby Closure
DEADLINE:
- 10, 15, 20, 36, 38, 55, 57, 61, 68
定義
- 関数を必要とする
- 親スコープで定義される変数を参照する
レキシカルバインディング
msg = "aaa" 3.times do prefix = "I" puts "#{prefix} #{msg}" end
I aaa I aaa I aaa
ブロックの内側から外側にはアクセスできる。
msg = "aaa" 3.times do prefix = "I" puts "#{prefix} #{msg}" end prefix
ブロックの外側から内側にアクセスできない。
自由変数
chalkboard_gag = lambda do |msg| lambda do prefix = "I will not" "#{prefix} #{msg}" end end chalkboard_gag inner_lambda = chalkboard_gag.call("drive the car") inner_lambda.call
I will not drive the car
例) カウンター
2つ目のlambdaから見ると、 x
は注入されてるので自由変数。
counter = lambda do x = 0 get_x = lambda { p x } # x is free variable incr = lambda { p x += 1 } decr = lambda { p x -= 1 } {get_x: get_x, incr: incr, decr: decr} end c1 = counter.call c1[:incr].call c1[:incr].call c1[:incr].call c1[:get_x].call c1[:decr].call c1[:decr].call c2 = counter.call c2[:get_x].call
1 2 3 3 2 1 0
コールバック関数
class Generator attr_reader :report def initialize(report) @report = report end def run report.to_csv end Notifier.new(Generator.new(good_report), on_success: lambda { |r| puts "Send #{r} to boss" }, on_failure: lambda { puts "Send to ben" } ).tap do |n| n.run end
is_even = lambda { |x| x % 2 == 0 }
is_even.call(3)
false
is_even = lambda { |x| x % 2 == 0 } def complement(predicate, value) not predicate.call(value) end complement(is_even, 3)
true
is_even = lambda { |x| x % 2 == 0 } def complement(predicate) lambda do |value| not predicate.call(value) end end complement(is_even).call(4) complement(is_even).call(5)
true
class Generator attr_reader :report def initialize(report) @report = report end def run report.to_csv end end class Notifier attr_reader :generator, :callbacks def initialize(generator, callbacks) @generator = generator @callbacks = callbacks end def run result = generator.run if result callbacks.fetch(:on_success).call(result) else callbacks.fetch(:on_failure).call end end end good_report = OpenStruct.new(to_csv: "59.99, Great Success") Notifier.new(Generator.new(good_report), on_success: lambda { |r| puts "Send #{r} to boss" }, on_failure: lambda { puts "Send email to ben" } ).tap do |n| n.run #=> send 59.99, great succes to boss end
good_report = OpenStruct.new(to_csv: nil) Notifier.new(Generator.new(good_report), on_success: lambda { |r| puts "Send #{r} to boss" }, on_failure: lambda { puts "Send email to ben" } ).tap do |n| n.run #=> ben end
元のNotifierクラスに手を加えることなく、ログ機能を追加できた。
reduce
既存のreduceの例。
[1, 2, 3, 4, 5].reduce(10) { |acc, x| p "#{acc}, #{x}"; acc + x }
25
eachを使わずに実装。再帰になる。
adder = lambda do |acc, arr| if arr.empty? acc else adder.call(acc + arr.first, arr.drop(1)) end end adder.call(10, [1, 2, 3, 4, 5])
25
multiplier = lambda do |acc, arr| if arr.empty? acc else multiplier.call(acc * arr.first, arr.drop(1)) end end multiplier.call(10, [1, 2, 3, 4, 5])
1200
変わったのは演算子だけで、DRYでない。 抽象化する。
reducer = lambda do |acc, arr, binary_function| if arr.empty? acc else reducer.call(binary_function.call(acc, arr.first), arr.drop(1), binary_function) end end reducer.call(1, [1, 2, 3, 4, 5], lambda { |x, y| x + y })
16
reducer = lambda do |acc, arr, binary_function| reducer_aux = lambda do |acc, arr| if arr.empty? acc else reducer_aux.call(binary_function.call(acc, arr.first), arr.drop(1)) end end reducer_aux.call(acc, arr) end reducer.call(1, [1, 2, 3, 4, 5], lambda { |x, y| x + y })
16
ex1
def is_larger_than(amount) lambda do |a| a > amount # amount is free variable end end larger_than_5 = is_larger_than(5) larger_than_5.call(7) larger_than_5.call(3)
false
new_db = lambda do db = {} insert = lambda do |key, value| p db.store(key, value) end dump = lambda { p db } delete = lambda do |key| p db.delete(key) end {insert: insert, dump: dump, delete: delete} end db = new_db.call db[:insert].call("this is key", "this is value") db[:dump].call db[:delete].call("this is key") db[:dump].call
“this is value” {“this is key”=>“this is value”} “this is value” {}
complement = lambda do |function| lambda do |arg| not function.call(arg) end end is_even = lambda { |x| x % 2 == 0 } complement.call(is_even).call(5)
true
この部分遅延させる感じが本質か。
畳み込み演算の配列バージョン。
[1, 2, 3, 4, 5].reduce(Array.new()) { |result, item| result << item * 2 }
[2, 4, 6, 8, 10]
ブロック
def do_it yield end do_it {"I'm doing it."}
I’m doing it.
def do_it yield end do_it { [1, 2, 3] << 4}
[1, 2, 3, 4]
def do_it(x, y) yield(x, y) end do_it(2, 3) { |x, y| x + y } do_it("Ohai", "Dictator") do |greeting, title| "#{greeting}, #{title}!!!" end
Ohai, Dictator!!!
def do_it(x) yield x end do_it(42) { |num, line| "#{num}: #{line}" }
42:
ブロックは無名関数に似ている。名前がかぶると外側にあっても上書きする。
x = "outside x" 1.times { x = "modified from the outside block" } x
modified from the outside block
ブロック変数を使うとブロック外を上書きしない。
x = "outside x" 1.times { |;x| x = "modified from the outside block" } x
outside x
Fixnum#times
↓みたいなことができるのはどうしてか。
3.times { puts "D'oh!" }
D’oh! D’oh! D’oh!
class Fixnum def times puts "This does nothing yet!" end end 3.times { puts "D'oh!" }
class Array def each end end %w(look ma no for loops).each do |x| puts x end
eachを作ってみる。
class Array def each x = 0 while x < self.length yield self[x] x += 1 end end end %w(look me no for loops).each do |x| puts x end # look # me # no # for # loops
IO close利用
ブロックはファイルクローズのし忘れ防止にも使える。これはどうやって実装しているか。
File.open() do |f| f << "aaa" end
実装してみる。
class File def self.open(name, mode) file = new(name, mode) return file unless block_given? yield(file) ensure file.close end end
オブジェクトの初期化
ブロックはオブジェクトの初期化にも使える。
module Twitter module REST class Client attr_accessor :consumer_key, :consumer_secret, :access_token, :access_token_secret def initialize yield self if block_given? end end end end client = Twitter::REST::Client.new do |config| config.consumer_key = "YOUR_CONSUMER_KEY" config.consumer_secret = "YOUR_CONSUMER_SECRET" config.access_token = "YOUR_ACCESS_TOKEN" config.access_token_secret = "YOUR_ACCESS_SECRET" end #<Twitter::REST::Client:0x000056204ff8f410 @consumer_key="YOUR_CONSUMER_KEY", @consumer_secret="YOUR_CONSUMER_SECRET", @access_token="YOUR_ACCESS_TOKEN", @access_token_secret="YOUR_ACCESS_SECRET">
class Router def initialize yield self end def match(route) puts route end end routes = Router.new do |r| r.match '/about' => 'home#about' r.match '/users' => 'users#index' end
Railsのrouterでやっているように、ここからどうやってレシーバーの r
を使わずに指定できるのか。
def foo yield self end foo do puts self end # => main
ブロック内のselfはブロックが定義されたところのselfになる。ということで、selfを変えたければブロックが定義されるコンテキストを変えなければならない。
class Router def initialize(&block) instance_eval &block end def match(route) puts route end end routes = Router.new do match '/about' => 'home#about' end
Routerコンテキストになるので、デフォルトレシーバーでmatchが呼べる。
オプションをハッシュで受け取る。
module Twitter module REST class Client attr_accessor :consumer_key, :consumer_secret, :access_token, :access_token_secret def initialize(options = {}, &block) options.each { |k, v| send("#{k}=", v) } instance_eval(&block) if block_given? end end end end client = Twitter::REST::Client.new({consumer_key: "YOUR_CONSUMER_KEY"}) do consumer_secret = "YOUR_CONSUMER_SECRET" access_token = "YOUR_ACCESS_TOKEN" access_token_secret = "YOUR_ACCESS_SECRET" end
オプションハッシュを使うか、ブロックを使うか、あるいは両方を使うか選択できる。
ex2
eachを使ってmapを実装する。
class Array def map array = [] each do |x| array.push(yield x) end array end end goal = %w(look ma no for loops).map do |x| x.upcase end p goal
each_wordを実装する。 例えば↓みたいな動作イメージ。
"Nothing lasts forever but cold November Rain".each_word do |x| puts x end # => Nothing # => lasts # => forever ...
class String def each_word split.each do |x| yield x end end end "Nothing lasts forever but cold November Rain".each_word do |x| puts x end
Active RecordのDSLを実装する。 例えば。
ActiveRecord::Schema.define(version: 20130314230445) do create_table "microposts", force: true do |t| t.string "content" t.integer "user_id" t.datetime "created_at" t.datetime "updated_at" end end
module ActiveRecord class Schema def self.define(version, &block) version instance_eval(&block) if block_given? end def self.create_table(table_name, options = {}, &block) t = Table.new(table_name, options) yield t if block_given? end end end class Table def initialize(name, options) @name = name @options = options end def string(value) puts "Creating column of type string named #{value}" end def integer(value) puts "Creating column of type integer named #{value}" end def datetime(value) puts "Creating column of type datetime named #{value}" end end ActiveRecord::Schema.define(version: 20130315230445) do create_table "microposts", force: true do |t| t.string "content" t.integer "user_id" t.datetime "created_at" t.datetime "updated_at" end end # Output # Creating column of type string named content # Creating column of type string named user_id # Creating column of type string named created_at # Creating column of type string named updated_at
Proc
ブロックは単体で存在できないが、ProcとLambdaは単体でオブジェクトとして存在できる。
p = proc { |x, y| x + y }
#<Proc:0x000055b7801c80d0 -:3>
p = Kernel.proc { |x, y| x + y }
#<Proc:0x0000557970bb7a58 -:3>
p = proc { |x, y| p x + y } p.call("oh", "ai") p.call(4, 2)
“ohai” 6
短縮記法もある。
p = proc { |x, y| p x + y } p.("oh", "ai") p.(1, 2)
“ohai” 3
この記法は、call()が実装されているクラスならどこでも使用できる。
class Carly def call(who) "call #{who}, maybe" end end c = Carly.new c.("me")
even = proc { |x| x % 2 == 0 } p even === 11 p even === 10
false true
Lambda
lambdaのクラスはProcである。
lambda {}.class
Proc
procのクラスはProcである。
proc {}.class
Proc
lambda { |x, y| x + y }.call(x, y) lambda { |x, y| x + y }[x, y] lambda { |x, y| x + y }.(x, y) lambda { |x, y| x + y } === [x, y] -> (x, y){ x + y }.call(x, y) -> (x, y){ x + y }[x, y] -> (x, y){ x + y }.(x, y) -> (x, y){ x + y } === [x, y]
->がよくラムダ計算表記に使われるのは、λに似てるかららしい。まじか。
procは引数の数が合ってなくてもエラーにならない。
l = lambda { |x, y| puts "x: #{x}, y: #{y}" } l.call("Ohai", "Gentle Reader") p = proc { |x, y| puts "x: #{x}, y: #{y}" } p.call("Ohai", "Gentle Reader") p.call("Ohai")
x: Ohai, y: Gentle Reader x: Ohai, y: Gentle Reader x: Ohai, y:
lambdaは引数の数が合ってないとエラーになる。
l = lambda { |x, y| puts "x: #{x}, y: #{y}" } l.call("Ohai")
-:3:in `block in main’: wrong number of arguments (given 1, expected 2) (ArgumentError) from -:4:in `main’ from -:6:in `<main>’
class SomeClass def method_that_calls_proc_or_lambda(procy) puts "calling #{proc_or_lambda(procy)} now!" procy.call puts "#{proc_or_lambda(procy)} gets called!" end def proc_or_lambda(proc_like_thing) proc_like_thing.lambda? ? "lambda" : "Proc" end end c = SomeClass.new c.method_that_calls_proc_or_lambda lambda { return } # OK c.method_that_calls_proc_or_lambda proc { return } # gets calledまで到達しない。procはmainコンテキストで作られる。
Symbol#to_proc
Rubyでは、 &
があるとprocに変換しようとする。
なので↓は。
["a", "i", "u"].map { |s| s.length }
[1, 1, 1]
省略記法で書ける。
["a", "i", "u"].map(&:length)
[1, 1, 1]
これは引数がProcでないのでエラーになる。 Objectクラスがprocへの変換のやり方を知らないため。
obj = Object.new
[1, 2, 3].map &obj
↓こうするとエラーにはならない。
class Object def to_proc proc {} end end obj = Object.new p [1, 2, 3].map &obj # => [nil, nil, nil]
class Object def to_proc proc { |x| "Here's #{x}!" } end end obj = Object.new p [1, 2, 3].map(&obj) # => ["Here's 1!", "Here's 2!", "Here's 3!"]
汎用化させる。
class Symbol def to_proc proc { |obj| obj.send(self) } end end p ["ai", "iue", "u"].map(&:length) # => [2, 3, 1] p ["ai", "iue", "u"].map(&:upcase) # => ["AI", "IUE", "U"]
p [1, 2, 3].inject(0) { |result, element| result + element } p [1, 2, 3].inject(&:+)
6 6
class Symbol def to_proc lambda { |obj, args| obj.send(self, *args) } end end p [1, 2, 3].inject(&:+)
カリー化
評価を遅延させること。
discriminant = lambda { |a| lambda { |b| lambda { |c| b **2 - 4*a*c } } } discriminant.call(5).call(6).call(7)
-104
同じ意味で、簡潔に書ける。
discriminant = lambda { |a, b, c| b**2 - 4*a*c }.curry
discriminant.call(5).call(6).call(7)
-104
これが利用できるシチュエーションを考える。 ↓は重複がたくさんある。
sum_ints = lambda do |start,stop| (start..stop).inject{ |sum,x| sum + x } end sum_of_squares= lambda do |start,stop| (start..stop).inject{ |sum,x| sum + x*x } end sum_of_cubes = lambda do |start,stop| (start..stop).inject{ |sum,x| sum + x*x*x} end
共通化できる。
sum = lambda do |fun, start, stop| (start..stop).inject { |sum, x| sum + fun.call(x) } end p sum_of_ints = sum.(lambda { |x| x }, 1, 10) p sum_of_square = sum.(lambda { |x| x*x }, 1, 10) p sum_of_cubes = sum.(lambda { |x| x*x*x }, 1, 10)
55 385 3025
さらにカリー化。
sum = lambda do |fun, start, stop| (start..stop).inject { |sum, x| sum + fun.call(x) } end sum_of_squares = sum.curry.(lambda { |x| x*x }) sum_of_squares.(1).(10) sum_of_squares.(50).(100)
295475
ex3
class Symbol def to_proc proc { |obj, args| obj.send(self, *args) } end end
"aaaa".send(:length)
4
to_procを初期化に使うことができる。
class SpiceGirl def initialize(name, nick) @name = name @nick = nick end def inspect "#{@name} (#{@nick} Spice)" end def self.to_proc proc { |obj| self.new(obj[0], obj[1]) } end end spice_girls = [["tarou", "T"], ["jirou", "J"]] p spice_girls.map(&SpiceGirl) # => [tarou (T Spice), jirou (J Spice)]
p proc {}.class p proc {}.lambda? p lambda {}.class p lambda {}.lambda? p -> {}.class p lambda {}.lambda?
Proc false Proc true Proc true
lambdaは引数の数が合わないとエラーになる。
j1 = proc { |x,y,z| "#{x}, #{y}, #{z}" } j2 = lambda { |x,y,z| "#{x}, #{y}, #{z}" } j1.call("hello", "world") # j2.call("hello", "world") # argument error
hello, world,
j1 = proc { |x,y,z| x + y + z } j2 = lambda { |x,y,z| x + y + z } # j1.call(1, 2) # -:3:in `+': nil can't be coerced into Integer (TypeError) # j2.call(1, 2) # -:4:in `block in main': wrong number of arguments (given 2, expected 3) (ArgumentError)
enumerable
- enumerable: 機能を持ったモジュール(ArrayとかHashと同列)。include先のクラスが持つ each メソッドを元に、様々なメソッドを提供する。
- enumerator: 実際にenumerateするオブジェクト。each 以外のメソッドにも Enumerable の機能を提供するためのラッパークラス。外部イテレータとしても使える。
p 1.upto(Float::INFINITY) # 評価せずオブジェクトを返す p 1.upto(5).to_a # 評価する # p 1.upto(Float::INFINITY).to_a # 処理は終わらない
#<Enumerator: 1:upto(Infinity)> [1, 2, 3, 4, 5]
p 1.upto(Float::INFINITY).lazy.map { |x| x * x } p 1.upto(Float::INFINITY).lazy.map { |x| x * x }.take(10) p 1.upto(Float::INFINITY).lazy.map { |x| x * x }.take(10).to_a
#<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator: 1:upto(Infinity)>>:map> #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator: 1:upto(Infinity)>>:map>:take(10)> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
internal iterationとexternal iteration
internalは、Arrayオブジェクトがiterateをコントロールする。戻れない。 externalは、包んでいる外部のオブジェクトがiterateをコントロールする。状態を持っているので戻ったり止めたりできる。
EnumeratorはEnumerableを包んでいる。 Arrayを入れてみる。
p e = Enumerator.new([1, 2, 3]) p e.next p e.next p e.next
#<Enumerator: [1, 2, 3]:each> 1 2 3
e = Enumerator.new do |yielder| [1, 2, 3].each do |val| yielder << val end end
fiberクラスは内部iteratorを外部iteratorに変換する。
f = Fiber.new do x = 0 loop do Fiber.yield x x += 1 end end p f.resume p f.resume p f.resume
0 1 2
EnumerableとEnumerator
module Enumerable def lax Lax.new(self) end end class Lax < Enumerator def initialize(receiver) super() do |yielder| receiver.each do |val| yielder << val end end end end e = 1.upto(Float::INFINITY).lax p e.next # 1 p e.next # 2
module Enumerable def lax Lax.new(self) end end class Lax < Enumerator def initialize(receiver) super() do |yielder| receiver.each do |val| puts "add: #{val}" yielder << val end end end end lax = Lax.new([1, 2, 3]) lax.map { |x| puts "map: #{x}; x" } # add: 1 # map: 1; x # add: 2 # map: 2; x # add: 3 # map: 3; x
lazy mapの実装。
module Enumerable def lax Lax.new(self) end end class Lax < Enumerator def initialize(receiver) super() do |yielder| receiver.each do |val| if block_given? yield(yielder, val) else yielder << val end end end end def map(&block) Lax.new(self) do |yielder, val| yielder << block.call(val) end end end p 1.upto(Float::INFINITY).lax.map { |x| x*x }.map { |x| x+1 }.first(5) # [2, 5, 10, 17, 26]
lazy takeの実装。
def take(n) taken = 0 Lax.new(self) do |yielder, val| if taken < n yielder << val taken += 1 else raise StopIteration end end end p 1.upto(Float::INFINITY).lax.take(5).first(5) # [1, 2, 3, 4, 5]
まとめ。
class Lax < Enumerator def initialize(receiver) super() do |yielder| receiver.each do |val| if block_given? yield(yielder, val) else yielder << val end end rescue StopIteration end end def map(&block) Lax.new(self) do |yielder, val| yielder << block.call(val) end end def take(n) taken = 0 Lax.new(self) do |yielder, val| if taken < n yielder << val taken += 1 else raise StopIteration end end end end p 1.upto(Float::INFINITY).lax.map { |x| x*x }.map { |x| x+1 }.first(5) p 1.upto(Float::INFINITY).lax.map { |x| x*x }.map { |x| x+1 }.take(5).to_a # ↑と結果は同じ
ex4
selectのlazy版。
def select(&block) Lax.new(self) do |yielder, val| if block.call(val) yielder << val end end end p 1.upto(Float::INFINITY).lax.take(5).select { |x| x % 2 == 0 }.to_a # => [2, 4]
dropのlazy版。
def drop(n) dropped = 0 Lax.new(self) do |yielder, val| if dropped < n dropped += 1 else yielder << val end end end p 1.upto(Float::INFINITY).lax.take(5).drop(3).to_a # => [4, 5]
Ruby kaigi 2021
VSCode extension for ruby type
- 言語自体を変えなくても、現代的なIDEの恩恵を受けられる。
- 特に静的型付け言語だと引数の型などを表示できる。
- ruby official type definition language
- 型レベルに抽象化して情報を解析する。
- https://github.com/usaito Special Thanksに載ってた人。年下だ…。本物の工学の人。
- あまり専門的な内容には触れなかった。
Why Ruby’s JIT was slow
- RubyのJITの状況、高速化。
- 方式の違い。
RuboCop in 2021: Stable and Beyond
- Rubocopの状況。過去、現在、未来。
- autocorrectが安全な修正をするように設計。
The Art of Execution Control for Ruby’s Debugger
- 歯のメンテナンス
- 新しいデバッガ:
debug.gem
Rails7からこれを使うようになるよう - rubyにおけるデバッガーの状況、ツール作った理由、使い方。
- gem
rdbg
info
コマンド- 一部分だけトレースできる。
- PostMortem debugging…検死、なぜプログラムが終了したか調べる。
- Record and play debug…戻れる。
Toycol: Define your own application protocol
- プロトコルの各レイヤーが責任を持つ
- プロトコルをサーバとクライアントが知っているものであれば、なんだって通信。自作プロトコルでも。
- 自作プロトコルの使い方と動作の仕組み
Regular Expressions: Amazing and Dangerous
なぜ危険か。
+?
によって非常に時間がかかるRegular Expressionになる可能性がある。文字列が非常に長い場合、組み合わせ数が爆発的に増えるため。- サービスがダウンすることもある。Stack Overflow, Cloudflare, Atom…であったインシデントのいくつか…はRubyのRegular Expression由来のものだった
- gemの中から危険な表現が使われているところを検索する。多くヒットした
対策。
//x
を使う- 正規表現のテストを書く。カバレッジは正規表現の中までは見ない…
- 入力の長さを制限する
Demystifying DSLs for better analysis and understanding
- Domain Specific Language
- Regular Expression, Rakefile, RSpec…
- Rails provide many DSL
- Tapioca gem
- generate rbi file from Model
The Future Shape of Ruby Objects
- Rubyのオブジェクトの実装を見ながら解説。
- オブジェクト指向言語Smalltalkのselfオブジェクト
- classとshape
- JavaScriptとかのプロトタイプ言語的アプローチ。
- Shopify/truffleruby
PRK Firmware: Keyboard is Essentially Ruby
- 自作キーボードを制御するfirmwareをRubyで書く
The newsletter of RBS updates
- ruby/rbs
- RBS → Rubyで型を定義するためのDSL
- サードパーティgemのRBSコレクションを作成している
- Railsに導入する方法
Parsing Ruby
- Rubyの記法の変遷。パーサの変遷
- コアに追従することは難しい
- 少しの文法の変更でも大きな影響範囲がある
- 少しの変更も拡張が難しい
Use Macro all the time ~ マクロを使いまくろ ~
- ASTレベルでRubyコードを置き換える
- パッケージの紹介
Charty: Statistical data visualization in Ruby
- Rubyでのグラフ描画ツール、charty
- パッケージの紹介
Dive into Encoding
- Relineのバグ修正で文字コードを深く知るきっかけ
- 文字コードを実装して学ぶ
- Coded Charcter Set
- Character Encoding Scheme
- Conversion table
- Encoding constant
How to develop the Standard Libraries of Ruby
- 標準ライブラリの作り方
- gemification - 本体添付からgemに切り離す
- rubygems/rubygems
Ruby, Ractor, QUIC
- QUICはGoogleによって開発された高速なプロトコル。
- クラウドゲーミングでは高速性が必要
- TCPとUDPの特性の違い
10 years of Ruby-powered citizen science
- Safecast/safecastapi: The app that powers api.safecast.org
- 放射線の観測デバイス
- デバイスが送信する観測データを各クラウドにキャストする
- Dashboardで加工、アクセスできるようにする
- マップ、グラフ、UI/UX、データバリデーション…課題はまだまだある
Matz Keynote
- Ruby 3.0
- 互換性大事
- 静的型付け言語が流行している。ほかの動的言語にも導入されている。Rubyにはどうか、答えはNo。
- 言語仕様としては型を実装することはない。周辺ツールで行う
- 型,LSP,チェッカ,…ツールを応援する
- パフォーマンスは重要。動機づけになる、問題を解決する
- パフォーマンスは評判に直結する
- マイクロベンチマーク(素数解析とか、単純な計算をもとにパフォーマンスを示す)は現実世界に影響するか → 実際にはしないけど、人々は信用しがちなので重要ではある
- Ruby3.XはRuby3.0より3倍早い
Graphical Terminal User Interface of Ruby 3.1
- 沢登り
- irbに補完機能をつける
Ruby Committers vs the World
- Rubyコミッターの人たちによる座談会
- cool
Source code
本体コードで気になったところのメモ。
概要
- 文法規則ファイル parse.y
- 実際に字句解析する関数 parser_yylex
- 予約語の一覧 ./defs/keywords
- RubyはC言語で用いられるLex字句解析ツールは使用しない。パフォーマンス、特殊なルールが必要だったために字句解析のコードを自前で用意している
- 字句解析->(トークン列)->構文解析->(ASTノード)->コンパイル。構文解析の段階でシンタックスチェックを行う
- Rubyをビルドする過程で、Bisonというparser generatorを使ってパーサコードを生成する。Bisonは文法規則ルール(parse.y)からパーサコード(parse.c)を生成する
Ripper
require 'ripper' require 'pp' code = <<STR 10.times do |n| puts n end STR puts code pp Ripper.lex(code)
10.times do |n| puts n end [[[1, 0], :on_int, “10”, END], [[1, 2], :on_period, “.”, DOT], [[1, 3], :on_ident, “times”, ARG], [[1, 8], :on_sp, “ ”, ARG], [[1, 9], :on_kw, “do”, BEG], [[1, 11], :on_sp, “ ”, BEG], [[1, 12], :on_op, “|”, BEG|LABEL], [[1, 13], :on_ident, “n”, ARG], [[1, 14], :on_op, “|”, BEG|LABEL], [[1, 15], :on_ignored_nl, “\n”, BEG|LABEL], [[2, 0], :on_sp, “ ”, BEG|LABEL], [[2, 2], :on_ident, “puts”, CMDARG], [[2, 6], :on_sp, “ ”, CMDARG], [[2, 7], :on_ident, “n”, END|LABEL], [[2, 8], :on_nl, “\n”, BEG], [[3, 0], :on_kw, “end”, END], [[3, 3], :on_nl, “\n”, BEG]]
Tasks
TODO APIデザインケーススタディ ――Rubyの実例から学ぶ。問題に即したデザインと普遍の考え方:書籍案内|技術評論社
RubyライブラリをテーマにしたAPI設計の本。
TODO Rubyのしくみ: Ruby Under a microscope
- JavaコードをJVM命令列にコンパイルするように、Rubyコード(ASTノード)をYARV命令列…Rubyコードを実行する仮想マシンにコンパイルする
- コンパイルして、C言語と機械語になる
- RubyVM::InstructionSequence で、Rubyでコードをどうコンパイルするかを確認できる
code = "a = 1+2; a" RubyVM::InstructionSequence.compile(code).disasm
== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,10)> (catch: FALSE) local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) [ 1] a@0 0000 putobject_INT2FIX_1_ ( 1)[Li] 0001 putobject 2 0003 opt_plus <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr] 0005 setlocal_WC_0 a@0 0007 getlocal_WC_0 a@0 0009 leave
計算に必要な1と2をpushしたあと、足し算している。
code = "a = 'hello' + 'world'; a" RubyVM::InstructionSequence.compile(code).disasm
== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,24)> (catch: FALSE) local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) [ 1] a@0 0000 putstring “hello” ( 1)[Li] 0002 putstring “world” 0004 opt_plus <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr] 0006 setlocal_WC_0 a@0 0008 getlocal_WC_0 a@0 0010 leave
文字列でも同様。
code = "a=[1, 2, 3]; a[0]=100;" RubyVM::InstructionSequence.compile(code).disasm
= disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,22)> (catch: FALSE)
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] a@0
0000 duparray [1, 2, 3] ( 1)[Li]
0002 setlocal_WC_0 a@0
0004 putnil
0005 getlocal_WC_0 a@0
0007 putobject_INT2FIX_0_
0008 putobject 100
0010 setn 3
0012 opt_aset <calldata!mid:[]
, argc:2, ARGS_SIMPLE>[CcCr]
0014 pop
0015 leave
TODO Metaprogramming All The Way Down
FactoryBotの詳細な説明。
TODO Enumerable#inject (Ruby 3.0.0 リファレンスマニュアル)
使えるようにしておく。まとめる。
TODO Ruby Operators
Rubyの特殊な演算子の名前と説明。
TODO Practical Ruby Project
- 254, 265, 272, 283
Rubyでのちょっと変わった、面白いプロジェクトを紹介している。
- 経済ゲームを作る
- Lispを実装
- パーサを実装
TODO Rubyソースコード完全解説
Rubyのコードの解説。
Archives
CLOSE 見てみるgemを選ぶ
まず探すのが大変なので、読んでみるgemを選ぶ。 手軽にできるのが良い。
曖昧なタスクなのでcloseする。
CLOSE おみくじ作り
Ruby上でLISPを実装しておみくじを作ってみる。
funcallを実装しようとして詰まる。単純な例だとできるようになったが、ネストしたときにうまく動いてない。 テストをちゃんと書くことと、デバッグ方法をちゃんとしないと厳しい。
1週間でできそうということではじめたが、時間がかかるので後回し。 言語実装に取り組むのはもっとも抽象的で難しい。だが無限の可能性を持っていてめちゃくちゃ楽しい。 もっとも難しいことをしたおかげで、ほかのことに自信をもって取り組みやすくなったように思える。ほとんどのことは言語を実装することに比べれば、簡単だ。
DONE 誤字修正
るりまの誤字を発見した。いくつか発見してまとめてPRを送ろう。
- 同じにように(Proc)
CLOSE rubocop issue(allow multiline)
https://github.com/rubocop/rubocop/issues/9365 どうにかなりそうではある。コメントルールをマルチラインに対応させる。
# これは検知される foo( # aaaa 22 ) # これはセーフ。これで間に合うように感じる。 foo( # bbbb 22 )
コメントのあとは空白行を無視したいらしいが、あまり意味を感じない。実装はできるが、目的があまりよくないように思える。
CLOSE rubocop issue(yoda expression)
https://github.com/rubocop/rubocop/issues/9222 New cop for yoda expressions.
TSLintにすでにあるので、実装の参考にすればいい。 Rule: binary-expression-operand-order
- 二項演算子(Binary Operator)
- 式を書いたときに、被演算子(変数とか値)が2つ登場する演算子
def on_send(node) method = node.method_name lhs = node.receiver rhs = node.first_argument # a.+(b) # a -> lhs # + -> method # b -> rhs end
conditionの方と合体させてもよさそう。TSLintはそうしてる。共通しているところは多い。全く別のcopにする方針で一応書けたが、本質的にcondition operatorとやってることは同じだ。
方式が違うので難しいな。明らかにTSLintのやり方が簡潔に書かれているように見える。rubocopの方はゴテゴテと条件が多い。単に対応オペレータを増やすだけだが、よくわからない。conditionを前提に書かれているところも難しい。
ちょっとやってどうにかなるものでなさそう。追加されないのには、理由があった。まず既存のがごちゃついてるので、それを整理する必要がある。
References
Ruby用語集 (Ruby 3.0.0)
おもしろい。
Building Enumerable & Enumerator in Ruby
Enumerableの詳しい解説。
ホワイの(感動的)Rubyガイド
ちょっと変わったRuby入門。
プログラミング言語 Ruby リファレンスマニュアル
rubyのドキュメント。
Rubular: a Ruby regular expression editor
Rubyの正規表現チェッカ。