Rubyはオブジェクト指向のProgramming Languageである。 WebフレームワークのRailsが有名で、採用されるケースはRailsを使うためが多い。
コンパイル不要なスクリプト言語、動的型付け、WEBに使用される、といった点でPythonと競合している。
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, :&, :|]
Enumerable#group_by
ブロックを評価した結果をキー、対応する要素の配列を値とするハッシュを返す。
QueryMethodの where
で取った値をハッシュにして、後で使いまわせる。N+1問題の回避に使える。QueryMethodぽい名前だが無関係。
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
がとても便利。すでにあるローカルにあるドキュメントを活用するべき。
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をそのまま使うと状況依存のテストになるため、毎回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 }
便利ツールを集めた 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 --jobs 4
などとして並列処理数を指定できる。
このマックスの数の調べ方。
getconf _NPROCESSORS_ONLN
なので、マシンごとで最速の設定で実行するためには。
bundle install --jobs `getconf _NPROCESSORS_ONLN`
とする。
https://stackoverflow.com/questions/39163758/bundle-how-many-parallel-jobs
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)
日付計算でDate同士を計算するときがある。 そのとき返ってくる値を表示すると-1/1みたいに表示される。 これはRational(有理数)オブジェクトである。 .to_iで整数に変換できる。
Date#- (Ruby 2.4.0 リファレンスマニュアル)
"foobar".scan(/../)
"1 2 3".scan(/\d+/)
"1, 2, 3".scan(/\d+/)
require 'objspace'
puts "#{ObjectSpace.memsize_of_all / (1000.0 * 1000.0)} MB"
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
Rubyで親クラスから子クラスの定数を参照 | EasyRamble
merge
メソッドはHashクラスのメソッドであり、配列では使えない。
単純な結合。
['a', 'b'] + ['a', 'b']
マージ(=かぶってたら削除)。
['a', 'b'] | ['a', 'b']
uniqでも同じ。
(['a', 'b'] + ['a', 'b']).uniq
structは簡易的なclassのようなもの。 データをまとめるのに使う。
user = Struct.new(:name, :age)
user.new('taro', 15)
【Ruby】Struct(構造体クラス)を理解する - Qiita
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
Hashが見づらいときは、 pp
を使うと綺麗に表示できる。
https://docs.ruby-lang.org/ja/latest/library/pp.html
mapの返り値は、ブロックの最後の値である。 だから↓みたく途中でセットしたい、というときは最後配列に入れたいものを置く必要がある。
options[:columns].map do |column|
od = OutDsl.new(column)
od.idx = index
od # ここ
end
mapは1行で書くこと多いので忘れがち。
https://rubygems.org/ であらかじめログインしておく。
curl -u {user名} https://rubygems.org/api/v1/api_key.yaml > ~/.gem/credentials; chmod 0600 ~/.gem/credentials
rake release
present?
の結果がtrueのときレシーバ自身を返す。falseのときはnilを返す。
object.present? ? object : nil
object.presense
これらは等価である。
処理に関わらずselfを返す。 メソッドチェーンへのデバッグに便利。
p ( 1 .. 5 )
.tap{|obj| puts obj.class}
.to_a.tap{|obj| puts obj.class}
メソッドチェーンの途中で分岐として使えそう。
配列から最大/最小の値を取りたいというとき、min_byが便利。
[5, -8, 3, 9].min_by{|num| num.abs }
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
オブジェクトに対して各種操作ができる。
- 10, 15, 20, 36, 38, 55, 57, 61, 68
- 関数を必要とする
- 親スコープで定義される変数を参照する
msg = "aaa"
3.times do
prefix = "I"
puts "#{prefix} #{msg}"
end
ブロックの内側から外側にはアクセスできる。
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
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
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)
is_even = lambda { |x| x % 2 == 0 }
def complement(predicate, value)
not predicate.call(value)
end
complement(is_even, 3)
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)
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の例。
[1, 2, 3, 4, 5].reduce(10) { |acc, x| p "#{acc}, #{x}"; acc + x }
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])
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])
変わったのは演算子だけで、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 })
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 })
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)
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
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)
この部分遅延させる感じが本質か。
畳み込み演算の配列バージョン。
[1, 2, 3, 4, 5].reduce(Array.new()) { |result, item| result << item * 2 }
def do_it
yield
end
do_it {"I'm doing it."}
def do_it
yield
end
do_it { [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
def do_it(x)
yield x
end
do_it(42) { |num, line| "#{num}: #{line}" }
ブロックは無名関数に似ている。名前がかぶると外側にあっても上書きする。
x = "outside x"
1.times { x = "modified from the outside block" }
x
ブロック変数を使うとブロック外を上書きしない。
x = "outside x"
1.times { |;x| x = "modified from the outside block" }
x
↓みたいなことができるのはどうしてか。
3.times { puts "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
ブロックはファイルクローズのし忘れ防止にも使える。これはどうやって実装しているか。
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
オプションハッシュを使うか、ブロックを使うか、あるいは両方を使うか選択できる。
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とLambdaは単体でオブジェクトとして存在できる。
p = proc { |x, y| x + y }
p = Kernel.proc { |x, y| x + y }
p = proc { |x, y| p x + y }
p.call("oh", "ai")
p.call(4, 2)
短縮記法もある。
p = proc { |x, y| p x + y }
p.("oh", "ai")
p.(1, 2)
この記法は、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
lambdaのクラスはProcである。
lambda {}.class
procのクラスはProcである。
proc {}.class
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")
lambdaは引数の数が合ってないとエラーになる。
l = lambda { |x, y| puts "x: #{x}, y: #{y}" }
l.call("Ohai")
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コンテキストで作られる。
Rubyでは、 &
があるとprocに変換しようとする。
なので↓は。
["a", "i", "u"].map { |s| s.length }
省略記法で書ける。
["a", "i", "u"].map(&:length)
これは引数が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(&:+)
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)
同じ意味で、簡潔に書ける。
discriminant = lambda { |a, b, c| b**2 - 4*a*c }.curry
discriminant.call(5).call(6).call(7)
これが利用できるシチュエーションを考える。 ↓は重複がたくさんある。
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)
さらにカリー化。
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)
class Symbol
def to_proc
proc { |obj, args| obj.send(self, *args) }
end
end
"aaaa".send(:length)
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?
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
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: 機能を持ったモジュール(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 # 処理は終わらない
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
internalは、Arrayオブジェクトがiterateをコントロールする。戻れない。 externalは、包んでいる外部のオブジェクトがiterateをコントロールする。状態を持っているので戻ったり止めたりできる。
EnumeratorはEnumerableを包んでいる。 Arrayを入れてみる。
p e = Enumerator.new([1, 2, 3])
p e.next
p e.next
p e.next
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
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 # ↑と結果は同じ
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]
- 言語自体を変えなくても、現代的なIDEの恩恵を受けられる。
- 特に静的型付け言語だと引数の型などを表示できる。
- RBS
- ruby official type definition language
- 型レベルに抽象化して情報を解析する。
- https://github.com/usaito Special Thanksに載ってた人。年下だ…。本物の工学の人。
- あまり専門的な内容には触れなかった。
- RubyのJITの状況、高速化。
- 方式の違い。
- Rubocopの状況。過去、現在、未来。
- autocorrectが安全な修正をするように設計。
- 歯のメンテナンス
- 新しいデバッガ:
debug.gem
Rails7からこれを使うようになるよう - rubyにおけるデバッガーの状況、ツール作った理由、使い方。
- gem
rdbg
info
コマンド- 一部分だけトレースできる。
- PostMortem debugging…検死、なぜプログラムが終了したか調べる。
- Record and play debug…戻れる。
- プロトコルの各レイヤーが責任を持つ
- プロトコルをサーバとクライアントが知っているものであれば、なんだって通信。自作プロトコルでも。
- 自作プロトコルの使い方と動作の仕組み
- CT装置
- 胆石
なぜ危険か。
+?
によって非常に時間がかかるRegular Expressionになる可能性がある。文字列が非常に長い場合、組み合わせ数が爆発的に増えるため。- サービスがダウンすることもある。Stack Overflow, Cloudflare, Atom…であったインシデントのいくつか…はRubyのRegular Expression由来のものだった
- gemの中から危険な表現が使われているところを検索する。多くヒットした
対策。
//x
を使う- 正規表現のテストを書く。カバレッジは正規表現の中までは見ない…
- 入力の長さを制限する
- Domain Specific Language
- Regular Expression, Rakefile, RSpec…
- Rails provide many DSL
- Tapioca gem
- generate rbi file from Model
- Rubyのオブジェクトの実装を見ながら解説。
- オブジェクト指向言語Smalltalkのselfオブジェクト
- classとshape
- JavaScriptとかのプロトタイプ言語的アプローチ。
- Shopify/truffleruby
- 自作キーボードを制御するfirmwareをRubyで書く
- ruby/rbs
- RBS → Rubyで型を定義するためのDSL
- サードパーティgemのRBSコレクションを作成している
- Railsに導入する方法
- Rubyの記法の変遷。パーサの変遷
- コアに追従することは難しい
- 少しの文法の変更でも大きな影響範囲がある
- 少しの変更も拡張が難しい
- ASTレベルでRubyコードを置き換える
- パッケージの紹介
- Rubyでのグラフ描画ツール、charty
- パッケージの紹介
- Relineのバグ修正で文字コードを深く知るきっかけ
- 文字コードを実装して学ぶ
- Coded Charcter Set
- Character Encoding Scheme
- Conversion table
- Encoding constant
- 標準ライブラリの作り方
- gemification - 本体添付からgemに切り離す
- rubygems/rubygems
- QUICはGoogleによって開発された高速なプロトコル。
- クラウドゲーミングでは高速性が必要
- TCPとUDPの特性の違い
- Safecast/safecastapi: The app that powers api.safecast.org
- 放射線の観測デバイス
- デバイスが送信する観測データを各クラウドにキャストする
- Dashboardで加工、アクセスできるようにする
- マップ、グラフ、UI/UX、データバリデーション…課題はまだまだある
- Ruby 3.0
- 互換性大事
- 静的型付け言語が流行している。ほかの動的言語にも導入されている。Rubyにはどうか、答えはNo。
- 言語仕様としては型を実装することはない。周辺ツールで行う
- 型,LSP,チェッカ,…ツールを応援する
- パフォーマンスは重要。動機づけになる、問題を解決する
- パフォーマンスは評判に直結する
- マイクロベンチマーク(素数解析とか、単純な計算をもとにパフォーマンスを示す)は現実世界に影響するか → 実際にはしないけど、人々は信用しがちなので重要ではある
- Ruby3x3
- Ruby3.XはRuby3.0より3倍早い
- 沢登り
- irbに補完機能をつける
- Rubyコミッターの人たちによる座談会
- cool
本体コードで気になったところのメモ。
- 文法規則ファイル parse.y
- 実際に字句解析する関数 parser_yylex
- 予約語の一覧 ./defs/keywords
- RubyはC言語で用いられるLex字句解析ツールは使用しない。パフォーマンス、特殊なルールが必要だったために字句解析のコードを自前で用意している
- 字句解析->(トークン列)->構文解析->(ASTノード)->コンパイル。構文解析の段階でシンタックスチェックを行う
- Rubyをビルドする過程で、Bisonというparser generatorを使ってパーサコードを生成する。Bisonは文法規則ルール(parse.y)からパーサコード(parse.c)を生成する
require 'ripper'
require 'pp'
code = <<STR
10.times do |n|
puts n
end
STR
puts code
pp Ripper.lex(code)
構文について。
RubyライブラリをテーマにしたAPI設計の本。
- JavaコードをJVM命令列にコンパイルするように、Rubyコード(ASTノード)をYARV命令列…Rubyコードを実行する仮想マシンにコンパイルする
- コンパイルして、C言語と機械語になる
- RubyVM::InstructionSequence で、Rubyでコードをどうコンパイルするかを確認できる
code = "a = 1+2; a"
RubyVM::InstructionSequence.compile(code).disasm
計算に必要な1と2をpushしたあと、足し算している。
code = "a = 'hello' + 'world'; a"
RubyVM::InstructionSequence.compile(code).disasm
文字列でも同様。
code = "a=[1, 2, 3]; a[0]=100;"
RubyVM::InstructionSequence.compile(code).disasm
FactoryBotの詳細な説明。
使えるようにしておく。まとめる。
Rubyの特殊な演算子の名前と説明。
- 254, 265, 272, 283
Rubyでのちょっと変わった、面白いプロジェクトを紹介している。
- 経済ゲームを作る
- Lispを実装
- パーサを実装
書籍リスト。
Rubyのコードの解説。
<2021-09-24 Fri> <2021-09-25 Sat> <2021-09-25 Sat> <2021-09-26 Sun> <2021-09-27 Mon> まず探すのが大変なので、読んでみるgemを選ぶ。 手軽にできるのが良い。曖昧なタスクなのでcloseする。
Ruby上でLISPを実装しておみくじを作ってみる。funcallを実装しようとして詰まる。単純な例だとできるようになったが、ネストしたときにうまく動いてない。 テストをちゃんと書くことと、デバッグ方法をちゃんとしないと厳しい。
1週間でできそうということではじめたが、時間がかかるので後回し。 言語実装に取り組むのはもっとも抽象的で難しい。だが無限の可能性を持っていてめちゃくちゃ楽しい。 もっとも難しいことをしたおかげで、ほかのことに自信をもって取り組みやすくなったように思える。ほとんどのことは言語を実装することに比べれば、簡単だ。
るりまの誤字を発見した。いくつか発見してまとめてPRを送ろう。- 同じにように(Proc)
# これは検知される
foo(
# aaaa
22
)
# これはセーフ。これで間に合うように感じる。
foo(
# bbbb
22
)
コメントのあとは空白行を無視したいらしいが、あまり意味を感じない。実装はできるが、目的があまりよくないように思える。
rubocop/rubocop#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を前提に書かれているところも難しい。
ちょっとやってどうにかなるものでなさそう。追加されないのには、理由があった。まず既存のがごちゃついてるので、それを整理する必要がある。
おもしろい。
Rubyのベストプラクティス集。
Enumerableの詳しい解説。
ちょっと変わったRuby入門。
rubyのドキュメント。
Rubyの正規表現チェッカ。