やってみる

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

組込ライブラリ(Struct)

 構造体。

成果物

情報源

Struct

構造体クラス。Struct.new はこのクラスのサブクラスを新たに生成します。

個々の構造体はサブクラスから Struct.new を使って生成します。個々の構造体サブクラスでは構造体のメンバに対するアクセスメソッドが定義されています。

メンバ抜粋

特異メソッド

[] members new

インスタンスメソッド

== [] []= dig each each_pair eql? equal? filter hash inspect length members select size to_a to_h to_s values values_at

new

new(*args, keyword_init: false) -> Class
new(*args, keyword_init: false) {|Class| block } -> Class

Struct クラスに新しいサブクラスを作って、それを返します。

 え、Classと同じなの?

サブクラスでは構造体のメンバに対するアクセスメソッドが定義されています。

dog = Struct.new("Dog", :name, :age)
fred = dog.new("fred", 5)
fred.age = 6
printf "name:%s age:%d", fred.name, fred.age
#=> "name:fred age:6" を出力します

実装の都合により、クラス名の省略は後づけの機能でした。メンバ名に String を指定できるのは後方互換性のためだと考えた方が良いでしょう。したがって、メンバ名は Symbol で指定するのが無難です。

引数 概要
args 構造体を定義するための可変長引数。String または Symbol を指定する
keyword_init trueを指定するとキーワード引数で初期化する構造体を定義する

第一引数が String の場合

args[0] が String の場合、クラス名になるので、大文字で始まる必要があります。つまり、以下のような指定はエラーになります。

p Struct.new('foo', 'bar')
# => -:1:in `new': identifier foo needs to be constant (NameError)

また args[1..-1] は、Symbol か String で指定します。

p Struct.new("Foo", :foo, :bar)   # => Struct::Foo

 同じ名前の構造体を生成したら以下警告が出た。

warning: redefining constant Struct::Foo

第一引数が Symbol の場合

args[0] が Symbol の場合、生成した構造体クラスは名前の無いクラスになります。名前の無いクラスは最初に名前を求める際に代入されている定数名を検索し、見つかった定数名をクラス名とします。

Foo = Struct.new(:foo, :bar)
p Foo                             # => Foo

 わかりにくい。第一引数は必ず構造体名にしてくれたらいいのに。なんだよ名前のない構造体って。

ブロックを指定した場合

Struct.new にブロックを指定した場合は定義した Struct をコンテキストにブロックを評価します。また、定義した Struct はブロックパラメータにも渡されます。

Customer = Struct.new(:name, :address) do
  def greeting
    "Hello #{name}!"
  end
end
Customer.new("Dave", "123 Main").greeting # => "Hello Dave!"

 メソッドも実装できるらしい。Structattr_accessorを省略できるClass定義みたいなもんか。

Structをカスタマイズする場合はこの方法が推奨されます。無名クラスのサブクラスを作成する方法でカスタマイズする場合は無名クラスが使用されなくなってしまうことがあるためです。

 意味わからん。

keyword_init: true を指定した場合

キーワード引数で初期化することを想定した構造体になります。

Point = Struct.new(:x, :y, keyword_init: true) # => Point(keyword_init: true)
Point.new(x: 1, y: 2) # => #<struct Point x=1, y=2>
Point.new(x: 1)       # => #<struct Point x=1, y=nil>
Point.new(y: 2)       # => #<struct Point x=nil, y=2>
Point.new(z: 3)       # ArgumentError (unknown keywords: z)

new(*args)

new(*args) -> Struct
self[*args] -> Struct

(このメソッドは Struct の下位クラスにのみ定義されています) 構造体オブジェクトを生成して返します。

構造体のメンバの数よりも多くの引数を指定した場合に発生します。

Foo = Struct.new(:foo, :bar)
foo = Foo.new(1)
p foo.values      # => [1, nil]

members

members -> [Symbol]

(このメソッドは Struct の下位クラスにのみ定義されています) 構造体のメンバの名前(Symbol)の配列を返します。

Foo = Struct.new(:foo, :bar)
p Foo.members      # => [:foo, :bar]

each_pair

 メンバと値の組を返す。

Foo = Struct.new(:foo, :bar)
Foo.new('FOO', 'BAR').each_pair {|m, v| p [m,v]}
# => [:foo, "FOO"]
#    [:bar, "BAR"]

所感

 メンバ変数だけならClass定義より少しだけ楽になるかも? でも大して変わらないからClass定義だけ知っていれば良さそう。

対象環境

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