BasicObject 클래스

BasicObject 클래스

BasicObject는 Ruby에 있는 모든 클래스의 부모 클래스예요. 명시적으로 비워 둔(bank) 클래스죠.

출처: Ruby 3.3 API

본문

BasicObject는 Ruby의 객체 계층과 독립적인 객체 계층을 만들 때, Delegator 클래스 같은 프록시 객체를 만들 때, 또는 Ruby의 메서드·클래스로 인한 네임스페이스 오염을 피해야 하는 다른 용도에 사용할 수 있어요.

다른 사용자를 위해 BasicObject를 오염시키지 않으려면, BasicObject를 직접 수정하는 대신 적절한 이름의 BasicObject 서브클래스를 만들어야 해요.

class MyObjectSystem < BasicObject
end

BasicObjectKernel을 포함하지 않아서(puts 같은 메서드가 없음), 표준 라이브러리의 네임스페이스 밖에 있으므로 전체 클래스 경로를 쓰지 않으면 일반적인 클래스를 찾을 수 없어요.

BasicObject의 서브클래스에 표준 라이브러리의 유용한 부분을 제공하는 전략은 다양해요. 서브클래스가 Kernel을 포함해 puts, exit 등을 얻을 수도 있고, Kernel 같은 커스텀 모듈을 만들어 포함하거나 method_missing으로 위임할 수도 있어요.

class MyObjectSystem < BasicObject
  DELEGATE = [:puts, :p]

  def method_missing(name, *args, &block)
    return super unless DELEGATE.include? name
    ::Kernel.send(name, *args, &block)
  end

  def respond_to_missing?(name, include_private = false)
    DELEGATE.include?(name) or super
  end
end

Ruby 표준 라이브러리의 클래스·모듈 접근은 BasicObject 서브클래스에서 ::File이나 ::Enumerator처럼 루트의 상수를 참조해 얻을 수 있어요. method_missing처럼 const_missing을 사용해 상수 검색을 Object에 위임할 수도 있어요.

class MyObjectSystem < BasicObject
  def self.const_missing(name)
    ::Object.const_get(name)
  end
end

BasicObject에 정의된 메서드는 다음과 같아요.

  • ::new: 새 BasicObject 인스턴스를 반환해요.
  • !: self의 부울 부정값인 true 또는 false를 반환해요.
  • !=: self와 주어진 객체가 같지 않은지 반환해요.
  • ==: self와 주어진 객체가 동등한지 반환해요.
  • __id__: self의 정수 객체 식별자를 반환해요.
  • __send__: 주어진 심볼로 식별된 메서드를 호출해요.
  • equal?: self와 주어진 객체가 같은 객체인지 반환해요.
  • instance_eval: self의 컨텍스트에서 주어진 문자열 또는 블록을 평가해요.
  • instance_exec: 주어진 인자를 전달하면서 self의 컨텍스트에서 주어진 블록을 실행해요.

Public Class Methods

new

BasicObject를 반환해요.

#define rb_obj_initialize rb_obj_dummy0

Public Instance Methods

!obj → true or false

부울 부정을 수행해요.

VALUE
rb_obj_not(VALUE obj)
{
    return RBOOL(!RTEST(obj));
}

obj != other → true or false

두 객체가 같지 않으면 true, 같으면 false를 반환해요.

VALUE
rb_obj_not_equal(VALUE obj1, VALUE obj2)
{
    VALUE result = rb_funcall(obj1, id_eq, 1, obj2);
    return rb_obj_not(result);
}

obj == other → true or false

eql?(other) → true or false

동등성(Equality) — Object 레벨에서 ==objother가 같은 객체일 때만 true를 반환해요. 보통 이 메서드는 자손 클래스에서 클래스 고유의 의미를 제공하도록 재정의돼요.

==와 달리 equal? 메서드는 객체 정체성(identity)을 판정하는 데 쓰이므로 서브클래스에서 재정의하면 안 돼요(a.equal?(b)ab와 같은 객체일 때만 참).

obj = "a"
other = obj.dup

obj == other      #=> true
obj.equal? other  #=> false
obj.equal? obj    #=> true

eql? 메서드는 objother가 같은 해시 키를 가리키면 true를 반환해요. Hash가 멤버의 동등성을 테스트할 때 사용돼요. eql?true를 반환하는 객체 쌍은 두 객체의 해시 값도 반드시 같아야 해요. 그래서 eql?을 재정의하는 서브클래스는 hash도 적절히 재정의해야 해요.

Object 클래스의 객체에서 eql?==와 동의어예요. 서브클래스는 보통 eql?을 자기 자신이 재정의한 ==에 별칭(alias)으로 두는 전통을 따르지만, 예외도 있어요. 예를 들어 Numeric 타입은 ==에서는 타입 변환을 수행하지만 eql?에서는 하지 않아요.

1 == 1.0     #=> true
1.eql? 1.0   #=> false
VALUE
rb_obj_equal(VALUE obj1, VALUE obj2)
{
    return RBOOL(obj1 == obj2);
}

equal?의 별칭이기도 해요.

id → integer

object_id → integer

obj에 대한 정수 식별자를 반환해요.

주어진 객체에 대해 object_id를 호출하면 항상 같은 번호가 반환되고, 활성 상태의 두 객체가 id를 공유하지는 않아요.

주의: 내장 클래스의 일부 객체는 최적화를 위해 재사용돼요. 즉시값(immediate value)과 동결된 문자열 리터럴이 그 경우예요.

BasicObject__id__를, Kernelobject_id를 구현해요.

즉시값은 참조로 전달되지 않고 값으로 전달돼요: nil, true, false, Fixnum, Symbol, 그리고 일부 Float가 그래요.

Object.new.object_id  == Object.new.object_id  # => false
(21 * 2).object_id    == (21 * 2).object_id    # => true
"hello".object_id     == "hello".object_id     # => false
"hi".freeze.object_id == "hi".freeze.object_id # => true
VALUE
rb_obj_id(VALUE obj)
{
    /*
     *                32-bit VALUE space
     *          MSB ------------------------ LSB
     *  false   00000000000000000000000000000000
     *  true    00000000000000000000000000000010
     *  nil     00000000000000000000000000000100
     *  undef   00000000000000000000000000000110
     *  symbol  ssssssssssssssssssssssss00001110
     *  object  oooooooooooooooooooooooooooooo00        = 0 (mod sizeof(RVALUE))
     *  fixnum  fffffffffffffffffffffffffffffff1
     *
     *                    object_id space
     *                                       LSB
     *  false   00000000000000000000000000000000
     *  true    00000000000000000000000000000010
     *  nil     00000000000000000000000000000100
     *  undef   00000000000000000000000000000110
     *  symbol   000SSSSSSSSSSSSSSSSSSSSSSSSSSS0        S...S % A = 4 (S...S = s...s * A + 4)
     *  object   oooooooooooooooooooooooooooooo0        o...o % A = 0
     *  fixnum  fffffffffffffffffffffffffffffff1        bignum if required
     *
     *  where A = sizeof(RVALUE)/4
     *
     *  sizeof(RVALUE) is
     *  20 if 32-bit, double is 4-byte aligned
     *  24 if 32-bit, double is 8-byte aligned
     *  40 if 64-bit
     */

    return rb_find_object_id(obj, cached_object_id);
}

send(symbol [, args...]) → obj

send(symbol [, args...]) → obj

send(string [, args...]) → obj

send(string [, args...]) → obj

symbol로 식별된 메서드를 호출하고, 지정된 인자를 전달해요. 메서드가 문자열로 식별되면 문자열은 심볼로 변환돼요.

BasicObject__send__를, Kernelsend를 구현해요. objSocket처럼 같은 이름의 메서드가 있을 때 __send__send보다 안전해요. public_send도 참고하세요.

class Klass
  def hello(*args)
    "Hello " + args.join(' ')
  end
end
k = Klass.new
k.send :hello, "gentle", "readers"   #=> "Hello gentle readers"
VALUE
rb_f_send(int argc, VALUE *argv, VALUE recv)
{
    return send_internal_kw(argc, argv, recv, CALL_FCALL);
}

equal?(other) → true or false

eql?(other) → true or false

동등성 — Object 레벨에서 ==objother가 같은 객체일 때만 true를 반환해요. 보통 이 메서드는 자손 클래스에서 클래스 고유의 의미를 제공하도록 재정의돼요.

==와 달리 equal? 메서드는 객체 정체성을 판정하는 데 쓰이므로 서브클래스에서 재정의하면 안 돼요.

obj = "a"
other = obj.dup

obj == other      #=> true
obj.equal? other  #=> false
obj.equal? obj    #=> true

eql? 메서드는 objother가 같은 해시 키를 가리키면 true를 반환해요. eql?을 재정의하는 서브클래스는 hash도 적절히 재정의해야 해요.

Object 클래스의 객체에서 eql?==와 동의어예요. Numeric 타입은 ==에서는 타입 변환을 수행하지만 eql?에서는 하지 않아요.

1 == 1.0     #=> true
1.eql? 1.0   #=> false

==의 별칭이에요.

instance_eval(string [, filename [, lineno]] ) → obj

instance_eval {|obj| block } → obj

Ruby 소스 코드를 담은 문자열 또는 주어진 블록을 리시버(obj)의 컨텍스트에서 평가해요. 컨텍스트를 설정하기 위해 코드가 실행되는 동안 변수 selfobj로 설정돼, 코드가 obj의 인스턴스 변수와 private 메서드에 접근할 수 있게 해요.

instance_eval에 블록이 주어지면 obj도 블록의 유일한 인자로 전달돼요.

instance_eval에 문자열이 주어지면 선택적 두 번째·세 번째 파라미터가 컴파일 오류를 보고할 때 사용되는 파일명과 시작 줄 번호를 제공해요.

class KlassWithSecret
  def initialize
    @secret = 99
  end
  private
  def the_secret
    "Ssssh! The secret is #{@secret}."
  end
end
k = KlassWithSecret.new
k.instance_eval { @secret }          #=> 99
k.instance_eval { the_secret }       #=> "Ssssh! The secret is 99."
k.instance_eval {|obj| obj == self } #=> true
static VALUE
rb_obj_instance_eval_internal(int argc, const VALUE *argv, VALUE self)
{
    return specific_eval(argc, argv, self, TRUE, RB_PASS_CALLED_KEYWORDS);
}

instance_exec(arg...) {|var...| block } → obj

주어진 블록을 리시버(obj)의 컨텍스트에서 실행해요. 코드가 실행되는 동안 변수 selfobj로 설정돼 코드가 obj의 인스턴스 변수에 접근할 수 있게 해요. 인자는 블록 파라미터로 전달돼요.

class KlassWithSecret
  def initialize
    @secret = 99
  end
end
k = KlassWithSecret.new
k.instance_exec(5) {|x| @secret+x }   #=> 104
static VALUE
rb_obj_instance_exec_internal(int argc, const VALUE *argv, VALUE self)
{
    return yield_under(self, TRUE, argc, argv, RB_PASS_CALLED_KEYWORDS);
}

Private Instance Methods

method_missing(symbol [, *args] ) → result

obj가 처리할 수 없는 메시지를 받았을 때 Ruby가 호출해요. symbol은 호출된 메서드를 나타내는 심볼이고, args는 전달된 인자들이에요. 기본적으로 인터프리터는 이 메서드가 호출되면 오류를 발생시켜요. 단, 이 메서드를 재정의해 더 동적인 동작을 제공할 수 있어요. 특정 메서드를 처리하지 않기로 했다면 super를 호출해 조상들이 빠진 메서드를 처리하게 해야 해요. 아래 예시는 로마 숫자로 된 이름의 메서드에 응답해 해당 정수 값을 반환하는 Roman 클래스를 만들어요.

class Roman
  def roman_to_int(str)
    # ...
  end

  def method_missing(symbol, *args)
    str = symbol.id2name
    begin
      roman_to_int(str)
    rescue
      super(symbol, *args)
    end
  end
end

r = Roman.new
r.iv      #=> 4
r.xxiii   #=> 23
r.mm      #=> 2000
r.foo     #=> NoMethodError
static VALUE
rb_method_missing(int argc, const VALUE *argv, VALUE obj)
{
    rb_execution_context_t *ec = GET_EC();
    raise_method_missing(ec, argc, argv, obj, ec->method_missing_reason);
    UNREACHABLE_RETURN(Qnil);
}

singleton_method_added(symbol)

리시버에 싱글턴 메서드가 추가될 때마다 콜백으로 호출돼요.

module Chatty
  def Chatty.singleton_method_added(id)
    puts "Adding #{id.id2name}"
  end
  def self.one()     end
  def two()          end
  def Chatty.three() end
end

이 코드는 이렇게 출력해요.

Adding singleton_method_added
Adding one
Adding three
#define rb_obj_singleton_method_added rb_obj_dummy1

singleton_method_removed(symbol)

리시버에서 싱글턴 메서드가 제거될 때마다 콜백으로 호출돼요.

module Chatty
  def Chatty.singleton_method_removed(id)
    puts "Removing #{id.id2name}"
  end
  def self.one()     end
  def two()          end
  def Chatty.three() end
  class << self
    remove_method :three
    remove_method :one
  end
end

이 코드는 이렇게 출력해요.

Removing three
Removing one
#define rb_obj_singleton_method_removed rb_obj_dummy1

singleton_method_undefined(symbol)

리시버에서 싱글턴 메서드가 정의되지 않음(undefined)으로 처리될 때마다 콜백으로 호출돼요.

module Chatty
  def Chatty.singleton_method_undefined(id)
    puts "Undefining #{id.id2name}"
  end
  def Chatty.one()   end
  class << self
     undef_method(:one)
  end
end

이 코드는 이렇게 출력해요.

Undefining one
#define rb_obj_singleton_method_undefined rb_obj_dummy1