Monday, April 03, 2006

[ruby] instance variable อีกที

object ทุก object ใน ruby สามารถมี instance variable ของตัวเองได้
2.instance_variable_set '@x', 1

2.
instance_variable_get '@x' # => 1
4.instance_variable_get '@x' # => nil
(4 - 2).instance_variable_get '@x' # => 1

บรรทัดที่น่าสนใจคือ (4 - 2).instance_vairable_get '@x'
ถ้าจิตนาการ การทำงานภายในของ interpreter ณ บรรทัดนั้น ดู
  • ขั้นแรกก็ต้อง new instance fixnum ค่าเป็น 4
  • จากนั้นก็ new instance fixnum ค่าเป็น 2
  • เรียกใช้ method - ของ instance 4 โดยมี parameter เป็น instance 2
  • เรียกใช้ method instance_variable_get บน instance fixnum 4

ปัญหาเกิดที่ขั้นที่ 3 ว่ามันเรียก instance_variable_get บน object คนละตัวกับ object 2
แล้วทำไมค่ามันยังออกมาถูก มันมีกลไกพิเศษอะไรตรงไหน

ลองเปลี่ยนจาก fixnum เป็น String ดูบ้าง
"hello".instance_variable_set '@x', 1
puts "hello".instance_variable_get '@x' # => nil
x = "hello"
x.instance_variable_set '@x', 1
puts x.instance_variable_get '@x' # => 1

จะเห็นได้ว่า instance_variable ของ 2 กับ "hello" มีพฤติกรรมไม่เหมือนกัน

ข่าวดี ไม่ใช่ทุกอย่างใน ruby ที่เป็น object อย่างเป็นทางการ
คำว่า object อย่างเป็นทางการ ก็คือ มี structure เป็นเรื่องเป็นราว เช่นมี id, methods, attributes
ถ้าจะให้ยกตัวอย่างชัดๆ ต้องดูที่ implementation ของ ruby ที่ใช้ c เขียน
struct RObject {
struct RBasic basic;
struct st_table *iv_tbl;
};

แต่ก็มีพวก special object ที่ไม่ได้ใช้ struct ในการ represent
object ต่อไปนี้ ใช้ unsigned long ตัวเดียวในการ implement object (แทนที่จะใช้ struct)
  • small integers
  • symbols
  • true
  • false
  • nil
  • Qundef (อันนี้ไม่รู้ว่ามันคืออะไร)

เหตุผลที่ต้อง implement special object แบบนี้ ก็คือเรื่อง performance
(เหมือนกับ java ที่ต้องมี primitive datatype)

ที่นี้ ruby สามารถให้ค่า instance_variable ที่ถูกต้อง ในกรณี fixnum ได้อย่างไร
ที่เป็นเช่นนี้ ก็เพราะวิธีการ implement ของ ruby เป็นแบบนี้

/* file variable.c */
static VALUE
ivar_get(obj, id, warn)
VALUE obj;
ID id;
int warn;
{
VALUE val;

switch (TYPE(obj)) {
case T_OBJECT:
case T_CLASS:
case T_MODULE:
if (ROBJECT(obj)->iv_tbl && st_lookup(ROBJECT(obj)->iv_tbl, id, &val))
return val;
break;
default:
if (FL_TEST(obj, FL_EXIVAR) || rb_special_const_p(obj))
return generic_ivar_get(obj, id, warn);
break;
}
if (warn) {
rb_warning("instance variable %s not initialized", rb_id2name(id));
}
return Qnil;
}

จะเห็นว่ากรณีของพวก special object จะมีการใช้ generic_ivar_get
ซึ่ง function generic_ivar_get นี้จะไปค้นหาค่าจาก global hashtable ที่ share กันใช้ ระหว่าง special object
นี่เป็นสาเหตุให้ fixnum 2 สามารถเรียกใ้ช้ instace_variable_get แล้ว ยังได้ค่าเดิมกลับมา

ส่วนกรณี String, จริงๆแล้ว มันตก case เดียวกับ fixnum
แต่วิธีการ implement ของ String เป็นอีกแบบหนึ่ง
ลองดูตัวอย่างนี้
irb(main):001:0> 2.object_id
5
irb(main):002:0> (4-2).object_id
5
irb(main):003:0> "hello".object_id
175214
irb(main):004:0> "hello".object_id
169884

จะเห็นได้ว่า id ของ string มันเปลี่ยนไปทุกครั้ง
ดังนั้นการ lookup instance_variable ใน generic table ที่ต้องใช้ object_id เป็นตัวเปิด hashtable
ก็เลยไม่ได้ค่าเดิมออกมา

Related link from Roti

5 comments:

Anonymous said...

python มั่ง

python ไม่ได้แยก special object กับ object ธรรมดา
แต่ object ที่จะเพิ่ม instance variable แบบ dynamicได้จะต้องเป็น object ที่มี attribute '__dict__'

>>> three = 3
>>> hasattr(three, '__dict__')
False
>>> three.x = 10
Traceback (most recent call last):
...
AttributeError: 'int' object has no attribute 'x'

>>> class MyInt(int): pass
>>> three = MyInt(3)
>>> hasattr(three, '__dict__')
True
>>> three.x = 10
>>> three.x
10

ส่วนเรื่อง identity ของ object อย่าง integer python ก็มีเหมือนกัน ตัวเลขช่วง -5 ถึง 99 ใน python จะ share ใช้ร่วมกันเสมอ (python 2.4)

>>> id(-6) == id(0-6)
False
>>> id(-5) == id(0-5)
True

>>> id(99) == id(0+99)
True
>>> id(100) == id(0+100)
False

PPhetra said...

ขอบคุณครับ, ชอบมาก ได้ความรู้ python

feature integer identity, น่าสนใจตรงที่ทำไมเขาเลือก
boundary ที่เลข -5
ทำไมไม่เป็น -10 หรือ -99

Anonymous said...

ตัวเลขติดลบที่ใช้บ่อยมากน่าจะเป็น -1
ส่วน -2, -3 น่าจะใช้ บ่อยในกรณีการ look-up array จากด้านท้าย

>>> 'hello'[-1]
'o'

>>> 'hello'[-2]
'l'

ตัวเลขติดลบมากๆอาจจะไม่ได้ใช้บ่อยจนต้องมา cache เก็บไว้ก็เป็นได้ครับ เค้าเลยเอาแค่ -5 ก็พอ

Anonymous said...

กำลังศึกษา rails ครับ อ่านหนังสือไปอ่านหนังสือ พร้อมกับค้นใน internet ซึ่งตอนนี้กำลังสับสนระว่าง instance variable กับ attribute ครับ มันหมายถึง อย่างเดียวกันไหม โดยเฉพาะ class ที่เป็น ActiveRecord ที่ map มาจาก table ครับ ช่วยอธิบายหน่อยครับ ผมอยากเข้าใจอย่างลึกซึ้งครับ

ลองดูจาก web นี้นะครับ
http://railstips.org/2006/11/18/class-and-instance-variables-in-ruby

PPhetra said...

instance variable เป้นตัวแปรภายในของ object หนึ่งๆ

attribute คือ คุณสมบัติ (ของ object นั้นๆ) ที่โลกภายนอกมองเห็น

ปกติ attribute ก็จะใช้ instance variable ในการเก็บ state(ข้อมูล) ของมัน

ส่วน activerecord พวก column ที่ map มาจาก table ถือว่าเป็น attribute (ส่วนการเก็บภายใน มันใช้ instace variable ที่เป็น type map ในการเก็บ value)