Class: R::Object
- Inherits:
- BasicObject
- Includes:
- BinaryOperators, ExecBinOp, ExecUniOp, IndexedObject, UnaryOperators
- Defined in:
- lib/R_interface/robject.rb
Direct Known Subclasses
Closure, DataFrame, Environment, Language, List, Matrix, RExpression, RSymbol, Vector
Constant Summary collapse
- EXPLICIT_RUBY_SURFACE =
Methods we explicitly define (Phase A / BasicObject). respond_to?(sym) is true for these and for any symbol we forward to R.
[ :r_interop, :expression, :expression=, :[], :[]=, :>>, :unboxed_get, :to_ruby, :to_i, :to_ary, :length, :size, :class, :to_s, :rclass, :typeof, :inspect, :object_id, :__id__, :==, :equal?, :call, :nil?, :pretty_print, :send, :instance_variable_set, :instance_variable_get, :is_a?, :kind_of?, :instance_of?, :respond_to?, :method_missing ].freeze
Instance Attribute Summary collapse
-
#expression ⇒ Object
Returns the value of attribute expression.
-
#r_interop ⇒ Object
readonly
Returns the value of attribute r_interop.
Class Method Summary collapse
-
.build(r_interop, expression = nil, r_class: nil, wrapper_tag: nil) ⇒ Object
Factory: build the right Ruby wrapper for an R value.
-
.build_class_probe_count ⇒ Object
Number of R
class()probes issued from Object.build (missing r_class and no usable wrapper_tag). - .build_from_r_class_string(r_interop, expression, r_class) ⇒ Object
- .build_from_wrapper_tag(r_interop, expression, tag, r_class_hint: nil) ⇒ Object
- .class_probe_fetch_r_class(r_interop) ⇒ Object
- .increment_build_class_probe_count! ⇒ Object
- .reset_build_counters! ⇒ Object
Instance Method Summary collapse
-
#==(other) ⇒ Object
Equality: R::Object vs R::Object uses all.equal; vs Ruby scalar uses unboxed value(s).
-
#call(*args) ⇒ Object
Call this object as an R function with the given args (e.g. obj.call(1, 2)).
-
#class ⇒ Object
Phase B: BasicObject has no class; we must define it.
-
#initialize(r_interop, expression = nil) ⇒ Object
constructor
A new instance of Object.
-
#inspect ⇒ Object
Debug string: class, object id, and @r_interop handle.
-
#instance_of?(mod) ⇒ Boolean
Exact class check (e.g. obj.instance_of?(R::Object)).
-
#instance_variable_get(name) ⇒ Object
Delegate to Kernel#instance_variable_get so that instance vars work despite BasicObject.
-
#instance_variable_set(name, value) ⇒ Object
Delegate to Kernel#instance_variable_set so that instance vars work despite BasicObject.
-
#is_a?(mod) ⇒ Boolean
(also: #kind_of?)
Type check against Ruby modules/classes (e.g. obj.is_a?(R::DataFrame)).
-
#method_missing(symbol, *args) ⇒ Object
Forward unknown methods to R: process_missing(symbol, self, *args) builds and runs the R call.
-
#nil? ⇒ Boolean
R objects are never nil; R NULL is converted to Ruby nil in Object.build.
-
#pretty_print(pp) ⇒ Object
Used by pp/inspect-style pretty printing.
-
#rclass ⇒ Object
R's class() of this object; unwrapped to a single string when length 1.
-
#respond_to?(sym, include_private = false) ⇒ Boolean
true for EXPLICIT_RUBY_SURFACE and for any other symbol (forwarded to R via method_missing).
-
#send(method_name, *args, &block) ⇒ Object
BasicObject lacks Kernel#send; provide Ruby dispatch explicitly so calls like obj.send(:mpg) do not get forwarded to R as send(obj, mpg).
-
#to_ary ⇒ Object
Return nil so RSpec/eq and array conversion don't forward to_ary to R (plain Object is not array-like).
-
#to_i ⇒ Object
Unbox length-1 numeric (or string digits) to Integer without forwarding to R (R has no to_i).
-
#to_ruby ⇒ Object
Simple way to get a Ruby value: (self >> nil).
-
#to_s ⇒ Object
Printed representation of the R object (as R would print it).
-
#typeof ⇒ Object
R's typeof() of this object; unwrapped to a single string when length 1.
-
#unboxed_get(index = nil, depth = 0) ⇒ Object
(also: #>>, #<<)
Unbox this R object to a Ruby value.
Methods included from ExecUniOp
Methods included from UnaryOperators
Methods included from ExecBinOp
Methods included from BinaryOperators
#!=, #%, #*, #**, #+, #-, #/, #<, #<=, #>, #>=, #_, #eq, #int_div, #til
Methods included from IndexedObject
Constructor Details
#initialize(r_interop, expression = nil) ⇒ Object
Returns a new instance of Object.
62 63 64 65 |
# File 'lib/R_interface/robject.rb', line 62 def initialize(r_interop, expression = nil) @r_interop = r_interop @expression = expression end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(symbol, *args) ⇒ Object
Forward unknown methods to R: process_missing(symbol, self, *args) builds and runs the R call.
405 406 407 |
# File 'lib/R_interface/robject.rb', line 405 def method_missing(symbol, *args) ::R::Support.process_missing(symbol, self, *args) end |
Instance Attribute Details
#expression ⇒ Object
Returns the value of attribute expression.
34 35 36 |
# File 'lib/R_interface/robject.rb', line 34 def expression @expression end |
#r_interop ⇒ Object (readonly)
Returns the value of attribute r_interop.
33 34 35 |
# File 'lib/R_interface/robject.rb', line 33 def r_interop @r_interop end |
Class Method Details
.build(r_interop, expression = nil, r_class: nil, wrapper_tag: nil) ⇒ Object
Factory: build the right Ruby wrapper for an R value. Returns a subclass (DataFrame, Vector, etc.), or the unwrapped Ruby value for rb_obj_* handles and for R NULL. Pass r_class to avoid an R class() call. Pass wrapper_tag from the new-bridge envelope to classify without string-matching r_class and to allow probe-free builds when r_class is absent but tag is present (non-+other+).
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/R_interface/robject.rb', line 85 def self.build(r_interop, expression = nil, r_class: nil, wrapper_tag: nil) # Ruby object handles stored in R: unwrap to the original Ruby object. if r_interop.is_a?(::String) && r_interop.start_with?("rb_obj_") return ::R::Support.get_ruby_object(r_interop) end # If we got a basic Ruby type, it's already unboxed return r_interop if r_interop.is_a?(::Numeric) || r_interop.is_a?(::TrueClass) || r_interop.is_a?(::FalseClass) || r_interop.nil? || r_interop.is_a?(::Symbol) if r_interop.is_a?(::String) && r_interop.start_with?("g2_v") return new(r_interop, expression) unless ::R.bridge.ready? begin rc_in = r_class rc = r_class.to_s.strip rc = nil if rc.nil? || rc.empty? tag = wrapper_tag.to_s.strip tag = nil if tag.nil? || tag.empty? # Probe only when we have no class string and no definitive wrapper tag. if rc.nil? && (!tag || tag == "other") rc = class_probe_fetch_r_class(r_interop) elsif rc.nil? && tag && tag != "other" # Envelope-style path: tag is enough; avoid class() round-trip. end return nil if rc && rc.strip == "NULL" if ::ENV['GALAAZ_DEBUG'] ::Kernel.puts "DEBUG: Object.build r_interop=#{r_interop.inspect} r_class_arg=#{rc_in.inspect} r_class=#{rc.inspect} wrapper_tag=#{tag.inspect}" end if tag && tag != "other" obj = build_from_wrapper_tag(r_interop, expression, tag, r_class_hint: rc) if obj ::Kernel.puts "DEBUG: Object.build returning #{obj.class}" if ::ENV['GALAAZ_DEBUG'] return obj end end if rc.nil? rc = class_probe_fetch_r_class(r_interop) return nil if rc.strip == "NULL" end obj = build_from_r_class_string(r_interop, expression, rc) ::Kernel.puts "DEBUG: Object.build returning #{obj.class}" if ::ENV['GALAAZ_DEBUG'] obj rescue => e ::Kernel.puts "DEBUG: Object.build rescue: #{e.}" if ::ENV['GALAAZ_DEBUG'] new(r_interop, expression) end else # Fallback for already unboxed or unexpected strings r_interop end end |
.build_class_probe_count ⇒ Object
Number of R class() probes issued from Object.build (missing r_class and no usable wrapper_tag).
Reset with reset_build_counters! before measuring. See docs/performance_plan.md Phase 2.
69 70 71 |
# File 'lib/R_interface/robject.rb', line 69 def self.build_class_probe_count @build_class_probe_count ||= 0 end |
.build_from_r_class_string(r_interop, expression, r_class) ⇒ Object
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
# File 'lib/R_interface/robject.rb', line 179 def self.build_from_r_class_string(r_interop, expression, r_class) case when r_class.include?("data.frame") || r_class.include?("tbl_df") ::R::DataFrame.new(r_interop, expression) when r_class.include?("matrix") || r_class.include?("array") ::R::Matrix.new(r_interop, expression) when r_class.include?("numeric") || r_class.include?("integer") || r_class.include?("logical") || r_class.include?("character") v = ::R::Vector.new(r_interop) v.expression = expression if expression v when r_class.include?("list") ::R::List.new(r_interop, expression) when r_class.include?("environment") ::R::Environment.new(r_interop, expression) when r_class.include?("function") ::R::Closure.new(r_interop, expression) when r_class.include?("language") || r_class == "call" ::R::Language.new(r_interop, expression) when r_class == "name" || r_class == "symbol" ::R::RSymbol.new(r_interop, expression) when r_class == "expression" ::R::RExpression.new(r_interop, expression) else ::Kernel.puts "DEBUG: Object.build fell through to else (r_class=#{r_class.inspect})" if ::ENV['GALAAZ_DEBUG'] new(r_interop, expression) end end |
.build_from_wrapper_tag(r_interop, expression, tag, r_class_hint: nil) ⇒ Object
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/R_interface/robject.rb', line 149 def self.build_from_wrapper_tag(r_interop, expression, tag, r_class_hint: nil) # typeof(formula) is language in R, but class is "formula"; keep plain R::Object (legacy semantics). if tag == "language" && r_class_hint && r_class_hint.include?("formula") return nil end case tag when "data_frame" ::R::DataFrame.new(r_interop, expression) when "matrix" ::R::Matrix.new(r_interop, expression) when "vector" v = ::R::Vector.new(r_interop) v.expression = expression if expression v when "list" ::R::List.new(r_interop, expression) when "environment" ::R::Environment.new(r_interop, expression) when "closure" ::R::Closure.new(r_interop, expression) when "language" ::R::Language.new(r_interop, expression) when "symbol" ::R::RSymbol.new(r_interop, expression) else nil end end |
.class_probe_fetch_r_class(r_interop) ⇒ Object
144 145 146 147 |
# File 'lib/R_interface/robject.rb', line 144 def self.class_probe_fetch_r_class(r_interop) increment_build_class_probe_count! ::R.bridge.eval_r("paste(class(#{r_interop}), collapse=' ')").gsub(/^\[1\] /, "").gsub(/"/, "").strip end |
.increment_build_class_probe_count! ⇒ Object
77 78 79 |
# File 'lib/R_interface/robject.rb', line 77 def self.increment_build_class_probe_count! @build_class_probe_count = build_class_probe_count + 1 end |
.reset_build_counters! ⇒ Object
73 74 75 |
# File 'lib/R_interface/robject.rb', line 73 def self.reset_build_counters! @build_class_probe_count = 0 end |
Instance Method Details
#==(other) ⇒ Object
Equality: R::Object vs R::Object uses all.equal; vs Ruby scalar uses unboxed value(s).
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 |
# File 'lib/R_interface/robject.rb', line 379 def ==(other) return true if self.equal?(other) if other.is_a?(::R::Object) res = ::R.bridge.eval_r("isTRUE(all.equal(#{@r_interop}, #{other.r_interop}))") return res == "[1] TRUE" elsif other.is_a?(::Numeric) || other.is_a?(::String) || other.is_a?(::TrueClass) || other.is_a?(::FalseClass) || other.nil? || other.is_a?(::Symbol) # DataFrame/Matrix etc. are not equal to a scalar even if they contain that value (e.g. row['mpg'] != 21.0). return false if self.class == ::R::DataFrame || self.class == ::R::Matrix val = (self >> nil) if val.is_a?(::Array) return val[0] == other if val.size == 1 return val.include?(other) if other.is_a?(::String) end return val == other else super end end |
#call(*args) ⇒ Object
Call this object as an R function with the given args (e.g. obj.call(1, 2)).
400 401 402 |
# File 'lib/R_interface/robject.rb', line 400 def call(*args) ::R::Support.exec_function(self, *args) end |
#class ⇒ Object
Phase B: BasicObject has no class; we must define it.
215 216 217 |
# File 'lib/R_interface/robject.rb', line 215 def class ::R::Object end |
#inspect ⇒ Object
Debug string: class, object id, and @r_interop handle.
240 241 242 |
# File 'lib/R_interface/robject.rb', line 240 def inspect "#<#{self.class}:#{__id__.to_s(16)} @r_interop=#{@r_interop.inspect}>" end |
#instance_of?(mod) ⇒ Boolean
Exact class check (e.g. obj.instance_of?(R::Object)).
234 235 236 237 |
# File 'lib/R_interface/robject.rb', line 234 def instance_of?(mod) return false unless mod.is_a?(::Module) self.class == mod end |
#instance_variable_get(name) ⇒ Object
Delegate to Kernel#instance_variable_get so that instance vars work despite BasicObject.
42 43 44 |
# File 'lib/R_interface/robject.rb', line 42 def instance_variable_get(name) ::Object.instance_method(:instance_variable_get).bind(self).call(name) end |
#instance_variable_set(name, value) ⇒ Object
Delegate to Kernel#instance_variable_set so that instance vars work despite BasicObject.
37 38 39 |
# File 'lib/R_interface/robject.rb', line 37 def instance_variable_set(name, value) ::Object.instance_method(:instance_variable_set).bind(self).call(name, value) end |
#is_a?(mod) ⇒ Boolean Also known as: kind_of?
Type check against Ruby modules/classes (e.g. obj.is_a?(R::DataFrame)).
227 228 229 230 |
# File 'lib/R_interface/robject.rb', line 227 def is_a?(mod) return false unless mod.is_a?(::Module) !!(self.class <= mod) end |
#nil? ⇒ Boolean
R objects are never nil; R NULL is converted to Ruby nil in Object.build.
53 54 55 |
# File 'lib/R_interface/robject.rb', line 53 def nil? false end |
#pretty_print(pp) ⇒ Object
Used by pp/inspect-style pretty printing.
58 59 60 |
# File 'lib/R_interface/robject.rb', line 58 def pretty_print(pp) pp.text(inspect) end |
#rclass ⇒ Object
R's class() of this object; unwrapped to a single string when length 1.
247 248 249 250 251 |
# File 'lib/R_interface/robject.rb', line 247 def rclass res = ::R::Support.exec_function("class", self) val = res.respond_to?(:>>) ? (res >> nil) : res val.is_a?(::Array) ? val[0] : val end |
#respond_to?(sym, include_private = false) ⇒ Boolean
true for EXPLICIT_RUBY_SURFACE and for any other symbol (forwarded to R via method_missing).
220 221 222 223 224 |
# File 'lib/R_interface/robject.rb', line 220 def respond_to?(sym, include_private = false) sym = sym.to_sym return true if EXPLICIT_RUBY_SURFACE.include?(sym) true end |
#send(method_name, *args, &block) ⇒ Object
BasicObject lacks Kernel#send; provide Ruby dispatch explicitly so calls like obj.send(:mpg) do not get forwarded to R as send(obj, mpg).
48 49 50 |
# File 'lib/R_interface/robject.rb', line 48 def send(method_name, *args, &block) ::Object.instance_method(:send).bind(self).call(method_name, *args, &block) end |
#to_ary ⇒ Object
Return nil so RSpec/eq and array conversion don't forward to_ary to R (plain Object is not array-like).
374 375 376 |
# File 'lib/R_interface/robject.rb', line 374 def to_ary nil end |
#to_i ⇒ Object
Unbox length-1 numeric (or string digits) to Integer without forwarding to R (R has no to_i). Callback procs often use x.to_i when R passes scalars as R::Object.
361 362 363 364 365 366 367 368 369 370 371 |
# File 'lib/R_interface/robject.rb', line 361 def to_i v = to_ruby v = v.first if v.is_a?(::Array) && v.size == 1 case v when ::Integer then v when ::Float then v.to_i when ::String then Integer(v) else Integer(v) end end |
#to_ruby ⇒ Object
Simple way to get a Ruby value: (self >> nil). For length-1 vectors returns the scalar; for longer returns array.
355 356 357 |
# File 'lib/R_interface/robject.rb', line 355 def to_ruby self >> nil end |
#to_s ⇒ Object
Printed representation of the R object (as R would print it).
209 210 211 212 |
# File 'lib/R_interface/robject.rb', line 209 def to_s return super unless ::R.bridge.ready? ::R.bridge.print_r(@r_interop) end |
#typeof ⇒ Object
R's typeof() of this object; unwrapped to a single string when length 1.
254 255 256 257 258 |
# File 'lib/R_interface/robject.rb', line 254 def typeof res = ::R::Support.exec_function("typeof", self) val = res.respond_to?(:>>) ? (res >> nil) : res val.is_a?(::Array) ? val[0] : val end |
#unboxed_get(index = nil, depth = 0) ⇒ Object Also known as: >>, <<
Unbox this R object to a Ruby value. Recurses until only Ruby values (no R::Object). Plain R::Object: re-evaluates handle; if list (typeof=="list") iterates [[i]] and recurses; if atomic uses [[index+1]] and result protocol (never uses $ on atomic). Raises UnboxDepthError when depth exceeded.
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 |
# File 'lib/R_interface/robject.rb', line 263 def unboxed_get(index = nil, depth = 0) if depth >= ::R::Support::MAX_UNBOX_DEPTH ::Kernel.raise(::R::UnboxDepthError, "unbox: list too deep (max depth #{::R::Support::MAX_UNBOX_DEPTH} exceeded)") end val = ::R::Support.eval(@r_interop) return val unless val.is_a?(::R::Object) return val.unboxed_get(index, depth + 1) if val.class.instance_method(:unboxed_get).owner != ::R::Object handle = @r_interop.to_s safe_handle = handle =~ /\Ag2_v\d+\z/ # Whole object (index.nil?): plain Object may be a list in R — use typeof/length, never $ if index.nil? type_str = self.typeof.to_s.strip len_obj = self.length n = len_obj.respond_to?(:unboxed_get) ? len_obj.unboxed_get(0) : len_obj n = n.to_i if n.respond_to?(:to_i) if type_str == "list" && n.is_a?(::Integer) && n >= 1 list_handle = handle unless safe_handle list_handle = ::R::Support.generate_var_name ::R.bridge.eval_r("#{list_handle} <- #{::R::Support.parse_arg(self)}") end arr = [] (1..n).each do |i| elt = ::R::Support.eval("#{list_handle}[[#{i}]]") if elt.nil? arr << nil elsif !elt.is_a?(::R::Object) arr << elt else arr << (elt.is__null.unboxed_get(0) ? nil : elt.unboxed_get(nil, depth + 1)) end end return arr end # Atomic (or length 0): treat as single element for consistency index = 0 end idx = index + 1 r_code = safe_handle ? "#{handle}[[#{idx}]]" : nil unless r_code var = ::R::Support.generate_var_name ::R.bridge.eval_r("#{var} <- #{::R::Support.parse_arg(self)}") r_code = "#{var}[[#{idx}]]" end val2 = ::R::Support.eval(r_code) return val2 unless val2.is_a?(::R::Object) return val2.unboxed_get(nil, depth + 1) if val2.class.instance_method(:unboxed_get).owner != ::R::Object # Plain R::Object single element: use result protocol until scalar or Vector/List r_code = "#{val2.r_interop}[[1]]" max_plain = ::R::Support::MAX_UNBOX_DEPTH - depth loop do var = ::R::Support.generate_var_name assignment = "#{var} <- #{r_code}" envelope = ::R.bridge.eval_r_with_result(assignment) raise "Result protocol: no envelope (buffer missing or invalid)" unless envelope case envelope[:type] when :scalar_double, :scalar_integer, :scalar_logical, :scalar_symbol return envelope[:value] when :scalar_character v = envelope[:value] return (v.is_a?(::String) && v =~ /^rb_obj_\d+$/) ? ::R::Support.get_ruby_object(v) : v when :handle obj = ::R::Object.build(envelope[:handle], nil, r_class: envelope[:r_class], wrapper_tag: envelope[:wrapper_tag]) unless obj.instance_of?(::R::Object) return obj.unboxed_get(nil, depth + 1) end r_code = "#{envelope[:handle]}[[1]]" max_plain -= 1 if max_plain <= 0 ::Kernel.raise(::R::UnboxDepthError, "unbox: list too deep (max depth #{::R::Support::MAX_UNBOX_DEPTH} exceeded)") end else raise "Result protocol: unknown envelope type #{envelope[:type].inspect}" end end end |