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)

https://github.com/rubocop/rubocop-jp/issues/33

日付計算でのRational

日付計算でDate同士を計算するときがある。 そのとき返ってくる値を表示すると-1/1みたいに表示される。 これはRational(有理数)オブジェクトである。 .to_iで整数に変換できる。

Date#- (Ruby 2.4.0 リファレンスマニュアル)

マッチした部分文字列の配列を返す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

Rubyで親クラスから子クラスの定数を参照 | EasyRamble

配列をマージ/結合

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>

【Ruby】Struct(構造体クラス)を理解する - Qiita

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: <2021-11-21 日>

  • 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

https://www.slideshare.net/mametter/typeprof-for-ide-enrich-development-experience-without-annotations

  • 言語自体を変えなくても、現代的な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 languageで用いられる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 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入門。

Rubular: a Ruby regular expression editor

Rubyの正規表現チェッカ。