やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

組込ライブラリ(BasicObject、Object)

 今日から組込ライブラリを学習してゆく。

成果物

情報源

BasicObject

特殊な用途のために意図的にほとんど何も定義されていないクラスです。

 知らなくてもよさそう。

基本的にはほぼすべてのクラスの親は Object と考えて差し支えありません。しかし、ある種のクラスを定義する際には Object クラスは持っているメソッドが多すぎる場合があります。

 はあ。

例えば、 BasicObject#method_missingを利用して Proxy パターンを実装する場合にはObject クラスに定義済みのメソッドはプロクシできないという問題が発生します。このような場合に Object ではなく BasicObject から派生して問題を解決できます。

 わからん! デザインパターンも学習せねば理解できない。

Object

全てのクラスのスーパークラス。オブジェクトの一般的な振舞いを定義します。

class C; end
C.ancestors #=> [C, Object, PP::ObjectMixin, Kernel, BasicObject]

 必ず以下が親になっているっぽい。

Object
↑
PP::ObjectMixin
↑
Kernel
↑
BasicObject

このクラスのメソッドは上書きしたり未定義にしない限り、すべてのオブジェクトで使用することができます。

 いくつか抜き出す。

  • clone/dup
  • freeze, frozen?
  • instance_of?(klass)/is_a?(mod),kind_of?(mod)
  • ni?
  • tap, then
  • ARGF, ARGV, ENV
  • メタ

clone/dup

 オブジェクトを複製する。

メソッド 概要 taint freeze 特異メソッド等
dup 浅いコピー(shallow copy)
clone 深いコピー(deep copy)

 taintselfを返す。Ruby3.2で削除予定

class C; end
c1 = C.new
c2 = c1.clone
class C; end
c1 = C.new
c2 = c1.dup

freeze, frozen?

オブジェクトを凍結(内容の変更を禁止)します。

 もしやオブジェクトに定義を追加することを禁止できる?

いったん凍結されたオブジェクトを元に戻す方法はありません。

 マジか。

凍結されるのはオブジェクトであり、変数ではありません。代入などで変数の指すオブジェクトが変化してしまうことは freeze では防げません。 freeze が防ぐのは、 `破壊的な操作' と呼ばれるもの一般です。変数への参照自体を凍結したい場合は、グローバル変数なら Kernel.#trace_var が使えます。

 つまりfreezeは値を不変(イミュータブル)にできるってこと?

s = 'ABC'
s.replace('xyz')
p s #=> "xyz"
s.freeze
s.replace('abc') #=> can't modify frozen String: "xyz" (FrozenError)

 freezeは破壊的操作を防げる。でも、代入による変更は防げない。

s = 'ABC'
s.freeze
s = 'xyz'
p s #=> "xyz"

 クラス定義の追加とかも禁止できるっぽい。

class C; end
C.freeze
class C
  def m; :m; end #=> can't modify frozen class: C (FrozenError)
end

 freezeをデフォルトにしたかった。コードすべてを読まないとどこで改ざんされるかわからなくなってしまう。ダッグタイピング的には改ざんできたほうが嬉しいのかもしれないけれど、大規模なコードになるとバグる可能性が常にある。デフォルトでfreezeにするコマンド引数とかあればよかったのに。シバンに引数セットすれば安全なコードが書ける。ダッグタイピングしたければその引数を外すだけ。とかだったらよかったのになぁ。

 frozen?は凍結されていたら真を返す。

class C; end
p C.frozen? #=> false
C.freeze
p C.frozen? #=> true

instance_of?(klass)/is_a?(mod),kind_of?(mod)

 instance_of?はオブジェクトがklassインスタンスであるなら真を返す。

class C; end
class D; end
C.new.instance_of?(C) #=> true
C.new.instance_of?(D) #=> false
class E < D; end
E.new.instance_of?(D) #=> false

 is_a?, kind_of?はオブジェクトが指定されたmodかそのサブクラスのとき真を返す。また、オブジェクトがmodをMixinしていたら真を返す。

class C; end
class D; end
C.new.is_a?(C) #=> true
C.new.is_a?(D) #=> false
class E < D; end
E.new.is_a?(D) #=> true
module M; end
class C
  include M
end
class D; end
C.new.is_a?(M) #=> true
D.new.is_a?(M) #=> false

ni?

 レシーバがnilなら真を返す。

class C; end
c = C.new
c.nil? #=> false

c = nil
c.nil? #= true

tap, then

 selfを引数としてブロックを評価する。メソッドチェーンを作る。tapselfを返す。thenはブロックの結果を返す。

(1..10)                  .tap {|x| puts "original: #{x}" }
  .to_a                  .tap {|x| puts "array:    #{x}" }
  .select {|x| x.even? } .tap {|x| puts "evens:    #{x}" }
  .map {|x| x*x }        .tap {|x| puts "squares:  #{x}" }
3.next.then {|x| x**x }.to_s             # => "256"
"my string".yield_self {|s| s.upcase }   # => "MY STRING"
require 'open-uri'
require 'json'

construct_url(arguments).
  yield_self {|url| URI(url).read }.
  yield_self {|response| JSON.parse(response) }

ブロックなしで呼び出されたときは Enumerator を返します。例えば条件によって値を捨てるのに使えます。

# 条件にあうので何もしない
1.yield_self.detect(&:odd?)            # => 1
# 条件に合わないので値を捨てる
2.yield_self.detect(&:odd?)            # => nil

 &:odd?はたぶんodd?が奇数なら真を返すメソッドなのだろう。そして&はブロック引数をあらわし、:はシンボルか。見慣れない指定の仕方。

ARGF, ARGV, ENV

定数 概要
ARGF 引数 (なければ標準入力) で構成される仮想ファイル。(詳細は ARGFARGF.class を参照)。
ARGV Ruby スクリプトに与えられた引数を表す配列。
ENV 環境変数を表す (擬似) 連想配列 (詳細は ENV を参照)。

メタ

 実装の確認に使える。たとえばextendしたときどのオブジェクトに追加されたのかを確認できる。

  • public_method[s]
  • protected_method[s]
  • private_method[s]
class C
  def m; end
end
C.new.public_mehods          #=> すべて
C.new.public_mehods false #=> 自分で定義したものだけ

 特異クラス/特異メソッド。

  • singleton_class
  • singleton_method[s]

所感

 ほかにも有用なメソッドや定数はあるが、ひとまずこれだけ。

対象環境

$ uname -a
Linux raspberrypi 5.10.52-v7l+ #1441 SMP Tue Aug 3 18:11:56 BST 2021 armv7l GNU/Linux