🍣

[Feature #21387] 各メンバーの要素にアクセスできる Data#[] を追加する提案

に公開

[Feature #21387] Proposal to add Data#[]

  • 各メンバーの要素にアクセスできる Data#[] を追加する提案
  • data.iddata[:id]data["id"] のようにより Hash ライクにアクセスできる提案ですね
  • また Hash とは異なり data[:invalid] のときに invalid がメンバーに存在しなければ NameError が発生する想定です
  • モチベーションとしてはダックタイピング的に #[] にアクセスできるようになると他の機能で利用しやすい、みたいな点があげられていますね
  • 例えば ActiveSupport の Enumerable#pluck など
# data_test.rb
require "bundler/inline"

gemfile do
  source "https://rubygems.org"
  gem "activesupport", "8.0.2"
end

require "active_support/core_ext/enumerable"

StructPerson = Struct.new(:name, :age)
DataPerson = Data.define(:name, :age)

struct_people = [
  StructPerson.new("Bob", 25),
]
puts "Struct:"

# #pluck では内部で obj[:name] を経由して値を取得する
puts "=> #{struct_people.pluck(:name)}"
# => ["Bob"]

data_people = [
  DataPerson.new("Charlie", 35),
]
puts "Data:"
# Data でも同要に #pluck を利用したい場合は Data#[] が定義されている必要がある
puts "=> #{data_people.pluck(:name)}"
  • これに関してなんですが Data の設計としては『可能な限りシンプルなアトミックオブジェクト』にすることで #[] を定義するとより『コンテナ』として扱われてしまう、のを危惧しているみたいですね
    • Data としては動的よりもより静的に近づける、みたいなイメージですかね?
      • コメントの中では OpenStruct も引き合いに出されてますね
    • #[] 以外でより Hash / Struct ライクにしないのか、みたいな話にも広がってくる、みたいな懸念点もあるみたい
    • https://bugs.ruby-lang.org/issues/21387#note-1
  • このあたりはどこまでなにを追加するのかが難しそうですねー Data#to_h を介せば Hash に変換できるのでそれで対応できますが、とはいえ都度 #to_h 呼び出すのも手間ですし
    • そもそも Data#[] を呼び出したいケースは限られている気もするし
  • 個人的にはダックタイピング的な意味で Data#[] が定義されている方が便利そうな気がしますがどうなんだろうな〜
    • #dig とかでも利用できれば嬉しい
  • 最終的にはより魅力的なユースケースがない限りは Data の拡張はやめるべき、みたいな結論になったみたいですね
  • もし、今回のような拡張を行いたい場合は次のように mixin して回避するコードも提示されています
# #[] でメソッドにアクセスできるような拡張モジュール
module DynamicallyAccessibleData
  def [](name)
    unless members.include?(name.to_sym)
      raise NameError, "no member '#{name}' in data"
    end
    public_send(name)
  end
end

# Data で定義したクラスに対して #[] でアクセスできるようなモジュールを mixin する
Person = Data.define(:name, :age).include(DynamicallyAccessibleData)
person = Person.new(name: "Charlie", age: 35)

# DynamicallyAccessibleData を mixin しておくことで #[] が利用できるようになる
person[:name] #=> "Charlie"
person[:age] #=> 35
person[:foo] #=> NameError: no member 'foo' in data
  • これだとそこそこの対応コストでいい感じに利用できそうですねー
GitHubで編集を提案

Discussion