class Data
Class Data 提供了一种便捷的方式来定义简单的、值相似对象的类。
最简单的用法示例
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 哈希转换,以及 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)
Data 不提供成员写入器或枚举器:它旨在存储不可变原子值。但请注意,如果任何数据成员是可变类的实例,Data 不会强制执行额外的不可变性。
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"]>
另请参阅 Struct,它是一个类似的概念,但具有更像容器的 API,允许更改对象内容并对其进行枚举。
Public Class Methods
Source
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(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;
}
定义一个新的 Data 类。
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">
请注意,不带成员的 Data 是可接受的,并且可能是一种有用的技术,用于定义几个同类的数据类,例如
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
Source
#define rb_data_s_members_m rb_struct_s_members_m
返回数据类的成员名称数组。
Measure = Data.define(:amount, :unit) Measure.members # => [:amount, :unit]
Source
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(self);
if (arg.unknown_keywords != Qnil) {
rb_exc_raise(rb_keyword_error_new("unknown", arg.unknown_keywords));
}
return Qnil;
}
使用 ::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">
所有参数都是强制的(与 Struct 不同),并被转换为关键字参数。
Measure.new(amount: 1) # in `initialize': missing keyword: :unit (ArgumentError) Measure.new(1) # in `initialize': missing keyword: :unit (ArgumentError)
请注意,Measure#initialize 始终接收关键字参数,并且强制参数在 initialize 中进行检查,而不是在 new 中。这对于重新定义 initialize 以转换参数或提供默认值可能很重要。
Measure = Data.define(:amount, :unit) class Measure 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 Measure::NONE>>
Public Instance Methods
Source
#define rb_data_equal rb_struct_equal
当 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
Source
#define rb_data_deconstruct rb_struct_to_a
以数组形式返回 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
Source
#define rb_data_deconstruct_keys rb_struct_deconstruct_keys
返回名称/值对的哈希,用于模式匹配。
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
Source
#define rb_data_eql rb_struct_eql
在两个数据项作为 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?。
Source
#define rb_data_hash rb_struct_hash
重新定义 Object#hash(用于区分对象作为 Hash 键) so that data objects of the same class with same content would have the same hash value, and represented the same Hash key.(以便同一类的具有相同内容的数据对象具有相同的 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
Source
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'] 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">
Source
#define rb_data_members_m rb_struct_members_m
以数组形式返回 self 中的成员名称。
Measure = Data.define(:amount, :unit) distance = Measure[10, 'km'] distance.members #=> [:amount, :unit]
Source
#define rb_data_to_h rb_struct_to_h
返回数据对象的 Hash 表示形式。
Measure = Data.define(:amount, :unit) distance = Measure[10, 'km'] distance.to_h #=> {:amount=>10, :unit=>"km"}
与 Enumerable#to_h 类似,如果提供了块,则期望它生成键值对来构造哈希。
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
返回 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">
Source
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);
}
返回 self 的浅拷贝 - self 的实例变量被复制,但它们引用的对象未被复制。
如果为该方法提供了任何关键字参数,则创建的副本将使用提供的关键字参数值更新 respective 字段值。请注意,如果提供了一个 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