類別資料
類別資料提供一個方便的方式來定義簡單類別以供類值物件使用。
最簡單的使用範例
Measure = Data.define(:amount, :unit) # Positional arguments constructor is provided distance = Measure.new(100, 'km') #=> #<data Measure amount=100, unit="km"> # Keyword arguments constructor is provided weight = Measure.new(amount: 50, unit: 'kg') #=> #<data Measure amount=50, unit="kg"> # Alternative form to construct an object: speed = Measure[10, 'mPh'] #=> #<data Measure amount=10, unit="mPh"> # Works with keyword arguments, too: area = Measure[amount: 1.5, unit: 'm^2'] #=> #<data Measure amount=1.5, unit="m^2"> # Argument accessors are provided: distance.amount #=> 100 distance.unit #=> "km"
建構的物件也有一個合理的定義為 ==
營運子,to_h
hash 轉換,以及 deconstruct
/ deconstruct_keys
用於模式配對。
::define
方法接受一個選用區塊,並在最新定義的類別的內容中評估它。這允許定義其他方法
Measure = Data.define(:amount, :unit) do def <=>(other) return unless other.is_a?(self.class) && other.unit == unit amount <=> other.amount end include Comparable end Measure[3, 'm'] < Measure[5, 'm'] #=> true Measure[3, 'm'] < Measure[5, 'kg'] # comparison of Measure with Measure failed (ArgumentError)
資料
不提供任何成員寫入器或列舉器:它的目的是作為不可變原子值的儲存。但請注意,如果某些資料成員屬於可變類別,資料
沒有額外的不可變性強制執行
Event = Data.define(:time, :weekdays) event = Event.new('18:00', %w[Tue Wed Fri]) #=> #<data Event time="18:00", weekdays=["Tue", "Wed", "Fri"]> # There is no #time= or #weekdays= accessors, but changes are # still possible: event.weekdays << 'Sat' event #=> #<data Event time="18:00", weekdays=["Tue", "Wed", "Fri", "Sat"]>
另請參閱 結構
,它是一個類似的概念,但具有更類似的容器 API,允許變更物件的內容並列舉它。
公開類別方法
定義一個新的資料類別。
measure = Data.define(:amount, :unit) #=> #<Class:0x00007f70c6868498> measure.new(1, 'km') #=> #<data amount=1, unit="km"> # It you store the new class in the constant, it will # affect #inspect and will be more natural to use: Measure = Data.define(:amount, :unit) #=> Measure Measure.new(1, 'km') #=> #<data Measure amount=1, unit="km">
請注意,沒有成員的資料是可以接受的,並且可能是定義幾個同質資料類別的有用技術,例如
class HTTPFetcher Response = Data.define(:body) NotFound = Data.define # ... implementation end
現在,來自 HTTPFetcher
的不同種類的回應會有相符的表示
#<data HTTPFetcher::Response body="<html..."> #<data HTTPFetcher::NotFound>
並且在模式配對中很方便使用
case fetcher.get(url) in HTTPFetcher::Response(body) # process body variable in HTTPFetcher::NotFound # handle not found case end
static VALUE rb_data_s_def(int argc, VALUE *argv, VALUE klass) { VALUE rest; long i; VALUE data_class; rest = rb_ident_hash_new(); RBASIC_CLEAR_CLASS(rest); for (i=0; i<argc; i++) { VALUE mem = rb_to_symbol(argv[i]); if (rb_is_attrset_sym(mem)) { rb_raise(rb_eArgError, "invalid data member: %"PRIsVALUE, mem); } if (RTEST(rb_hash_has_key(rest, mem))) { rb_raise(rb_eArgError, "duplicate member: %"PRIsVALUE, mem); } rb_hash_aset(rest, mem, Qtrue); } rest = rb_hash_keys(rest); RBASIC_CLEAR_CLASS(rest); OBJ_FREEZE_RAW(rest); data_class = anonymous_struct(klass); setup_data(data_class, rest); if (rb_block_given_p()) { rb_mod_module_eval(0, 0, data_class); } return data_class; }
傳回資料類別的成員名稱陣列
Measure = Data.define(:amount, :unit) Measure.members # => [:amount, :unit]
#define rb_data_s_members_m rb_struct_s_members_m
使用 ::define
定義的類別的建構函式接受位置和關鍵字引數。
Measure = Data.define(:amount, :unit) Measure.new(1, 'km') #=> #<data Measure amount=1, unit="km"> Measure.new(amount: 1, unit: 'km') #=> #<data Measure amount=1, unit="km"> # Alternative shorter initialization with [] Measure[1, 'km'] #=> #<data Measure amount=1, unit="km"> Measure[amount: 1, unit: 'km'] #=> #<data Measure amount=1, unit="km">
所有引數都是強制性的(與 結構
不同),並轉換為關鍵字引數
Measure.new(amount: 1) # in `initialize': missing keyword: :unit (ArgumentError) Measure.new(1) # in `initialize': missing keyword: :unit (ArgumentError)
請注意,Measure#initialize
總是接收關鍵字引數,並且在 initialize
中檢查強制性引數,而不是在 new
中。這對於重新定義初始化以轉換引數或提供預設值非常重要
Measure = Data.define(:amount, :unit) do NONE = Data.define def initialize(amount:, unit: NONE.new) super(amount: Float(amount), unit:) end end Measure.new('10', 'km') # => #<data Measure amount=10.0, unit="km"> Measure.new(10_000) # => #<data Measure amount=10000.0, unit=#<data NONE>>
static VALUE rb_data_initialize_m(int argc, const VALUE *argv, VALUE self) { VALUE klass = rb_obj_class(self); rb_struct_modify(self); VALUE members = struct_ivar_get(klass, id_members); size_t num_members = RARRAY_LEN(members); if (argc == 0) { if (num_members > 0) { rb_exc_raise(rb_keyword_error_new("missing", members)); } return Qnil; } if (argc > 1 || !RB_TYPE_P(argv[0], T_HASH)) { rb_error_arity(argc, 0, 0); } if (RHASH_SIZE(argv[0]) < num_members) { VALUE missing = rb_ary_diff(members, rb_hash_keys(argv[0])); rb_exc_raise(rb_keyword_error_new("missing", missing)); } struct struct_hash_set_arg arg; rb_mem_clear((VALUE *)RSTRUCT_CONST_PTR(self), num_members); arg.self = self; arg.unknown_keywords = Qnil; rb_hash_foreach(argv[0], struct_hash_set_i, (VALUE)&arg); // Freeze early before potentially raising, so that we don't leave an // unfrozen copy on the heap, which could get exposed via ObjectSpace. OBJ_FREEZE_RAW(self); if (arg.unknown_keywords != Qnil) { rb_exc_raise(rb_keyword_error_new("unknown", arg.unknown_keywords)); } return Qnil; }
公開執行個體方法
如果 other
與 self
是同一個類別,且所有成員都相等,則傳回 true
。
範例
Measure = Data.define(:amount, :unit) Measure[1, 'km'] == Measure[1, 'km'] #=> true Measure[1, 'km'] == Measure[2, 'km'] #=> false Measure[1, 'km'] == Measure[1, 'm'] #=> false Measurement = Data.define(:amount, :unit) # Even though Measurement and Measure have the same "shape" # their instances are never equal Measure[1, 'km'] == Measurement[1, 'km'] #=> false
#define rb_data_equal rb_struct_equal
傳回 self
中的值作為陣列,用於模式比對
Measure = Data.define(:amount, :unit) distance = Measure[10, 'km'] distance.deconstruct #=> [10, "km"] # usage case distance in n, 'km' # calls #deconstruct underneath puts "It is #{n} kilometers away" else puts "Don't know how to handle it" end # prints "It is 10 kilometers away"
也可以檢查類別
case distance in Measure(n, 'km') puts "It is #{n} kilometers away" # ... end
#define rb_data_deconstruct rb_struct_to_a
傳回名稱/值配對的 hash,用於模式比對。
Measure = Data.define(:amount, :unit) distance = Measure[10, 'km'] distance.deconstruct_keys(nil) #=> {:amount=>10, :unit=>"km"} distance.deconstruct_keys([:amount]) #=> {:amount=>10} # usage case distance in amount:, unit: 'km' # calls #deconstruct_keys underneath puts "It is #{amount} kilometers away" else puts "Don't know how to handle it" end # prints "It is 10 kilometers away"
也可以檢查類別
case distance in Measure(amount:, unit: 'km') puts "It is #{amount} kilometers away" # ... end
#define rb_data_deconstruct_keys rb_struct_deconstruct_keys
當兩個資料項目是 Hash
的金鑰時,會使用相等性檢查。
與 ==
的細微差別在於,成員也會與其 eql?
方法進行比較,這在某些情況下可能很重要
Measure = Data.define(:amount, :unit) Measure[1, 'km'] == Measure[1.0, 'km'] #=> true, they are equal as values # ...but... Measure[1, 'km'].eql? Measure[1.0, 'km'] #=> false, they represent different hash keys
另請參閱 Object#eql?
以進一步說明方法用法。
#define rb_data_eql rb_struct_eql
重新定義 Object#hash
(用於區分物件為 Hash
金鑰),以便具有相同內容的相同類別資料物件具有相同的 hash
值,並表示相同的 Hash
金鑰。
Measure = Data.define(:amount, :unit) Measure[1, 'km'].hash == Measure[1, 'km'].hash #=> true Measure[1, 'km'].hash == Measure[10, 'km'].hash #=> false Measure[1, 'km'].hash == Measure[1, 'm'].hash #=> false Measure[1, 'km'].hash == Measure[1.0, 'km'].hash #=> false # Structurally similar data class, but shouldn't be considered # the same hash key Measurement = Data.define(:amount, :unit) Measure[1, 'km'].hash == Measurement[1, 'km'].hash #=> false
#define rb_data_hash rb_struct_hash
傳回 self
的字串表示形式
Measure = Data.define(:amount, :unit) distance = Measure[10, 'km'] p distance # uses #inspect underneath #<data Measure amount=10, unit="km"> puts distance # uses #to_s underneath, same representation #<data Measure amount=10, unit="km">
static VALUE rb_data_inspect(VALUE s) { return rb_exec_recursive(inspect_struct, s, rb_str_new2("#<data ")); }
傳回 self
中的成員名稱作為陣列
Measure = Data.define(:amount, :unit) distance = Measure[10, 'km'] distance.members #=> [:amount, :unit]
#define rb_data_members_m rb_struct_members_m
傳回資料物件的 Hash
表示形式。
Measure = Data.define(:amount, :unit) distance = Measure[10, 'km'] distance.to_h #=> {:amount=>10, :unit=>"km"}
就像 Enumerable#to_h
,如果提供區塊,則預期它會產生用於建構 hash 的金鑰值配對
distance.to_h { |name, val| [name.to_s, val.to_s] } #=> {"amount"=>"10", "unit"=>"km"}
請注意,to_h
和 initialize 之間存在有用的對稱性
distance2 = Measure.new(**distance.to_h) #=> #<data Measure amount=10, unit="km"> distance2 == distance #=> true
#define rb_data_to_h rb_struct_to_h
傳回 self
的字串表示形式
Measure = Data.define(:amount, :unit) distance = Measure[10, 'km'] p distance # uses #inspect underneath #<data Measure amount=10, unit="km"> puts distance # uses #to_s underneath, same representation #<data Measure amount=10, unit="km">
傳回 self
的淺層拷貝,複製 self
的實例變數,但不複製它們所參照的物件。
如果方法提供任何關鍵字引數,拷貝將會使用提供的關鍵字引數值來更新各自的欄位值而建立。請注意,提供 Data
類別沒有的關鍵字是錯誤的。
Point = Data.define(:x, :y) origin = Point.new(x: 0, y: 0) up = origin.with(x: 1) right = origin.with(y: 1) up_and_right = up.with(y: 1) p origin # #<data Point x=0, y=0> p up # #<data Point x=1, y=0> p right # #<data Point x=0, y=1> p up_and_right # #<data Point x=1, y=1> out = origin.with(z: 1) # ArgumentError: unknown keyword: :z some_point = origin.with(1, 2) # ArgumentError: expected keyword arguments, got positional arguments
static VALUE rb_data_with(int argc, const VALUE *argv, VALUE self) { VALUE kwargs; rb_scan_args(argc, argv, "0:", &kwargs); if (NIL_P(kwargs)) { return self; } VALUE h = rb_struct_to_h(self); rb_hash_update_by(h, kwargs, 0); return rb_class_new_instance_kw(1, &h, rb_obj_class(self), TRUE); }