Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

ใช้ Objects เป็น Hash Keys ใน Ruby: ฟีเจอร์พลังที่มองข้าม

หากติดตาม Ruby 3x3 effort คงเคยได้ยิน Optcarrot คือ NES emulator เขียนด้วย Ruby มurniture

ผมอ่าน source Optcarrot เน้นรายละเอียดน่าสนใจ: ใช้ฟีเจอร์ของ Ruby hashes ที่มองข้ามบ่อย แต่มีประโยชน์มาก คือ ความสามารถใช้ object ใดๆ เป็น hash key

บริบท: NES Memory Mapping

ในฐานะ high-level programmers เราคิด memory คือ RAM แต่ระดับต่ำ "memory" มี用途อื่นมาก

การอ่าน/เขียน "memory" เป็นวิธี CPU ของ NES สื่อสารกับ GPU, control pads และอิเล็กทรอนิกส์พิเศษบน cartridge ขึ้นอยู่กับ address เรียก write_to_memory อาจ reset joystick, swap VRAM หรือเล่นเสียง

จะ implement ใน Ruby ยังไง?

Optcarrot ทำโดยเก็บ Method objects สองตัวสำหรับแต่ละ address 65536 addresses หนึ่ง getter หนึ่ง setter 長相似:

@getter_methods[0x0001] = @ram.method(:[])
@setter_methods[0x0001] = @ram.method(:[]=)

ปัญหา: Duplicate Objects

ปัญหาใช้ Object#method แบบนี้: สร้าง Method objects เยอะที่เหมือนกัน

เห็นจาก object_id:

> a = []
> a.method(:[]=).object_id
=> 70142391223600
> a.method(:[]=).object_id
=> 70142391912420

สอง Method objects มี object_id ต่างกัน จึงเป็น object ต่างกัน แม้ทำงานเหมือนกัน

ปกติไม่สนใจ Method objects เพิ่มเติม แต่ที่นี่มีหลายพันตัว

แก้ไข: Memoizing ผ่าน Hash

Optcarrot หลีกเลี่ยง duplicate Method objects ด้วยเทคนิคง่ายมากจนมองข้ามง่าย

ใช้ hash เพื่อ memoize และ deduplicate โค้ดลดรูปด้านล่างสาธิตเทคนิค:

def initialize
  @setter_methods = []
  @setter_cache = {}
  ...
end

def add_setter(address, setter)
  # ไม่เก็บ duplicates
  @setter_cache[setter] ||= setter
  # ใช้ version ที่ deduped แล้ว
  @setter_methods[address] = @setter_cache[setter]
end

ทำงานได้เพราะ Hash ไม่สนใจ object ใดๆ ที่ให้เป็น keys

หากสับสน ลองใน IRB ด้วย strings:

> cache = {}
> cache["foo"] ||= "bar"
=> "bar"
> cache["foo"] ||= "baz"
=> "bar"

ใน Ruby strings เป็น instances ของ class String กลไกที่ Ruby ใช้ string เป็น hash key 基本上เหมือนกับที่ใช้เก็บ Method object

Hash คำนวณ equality ยังไง

ใช้ non-string objects เป็น hash keys คำถาม: Hash รู้ยังไงว่าสอง objects เท่ากัน?

คำตอบ: ใช้เมธอด Object#hash เมธอดนี้เดินผ่าน object และ recursive generate hash คล้าย:

> a.method(:[]=).hash
=> 929915641391564853

เพราะ identical objects ผลิต hash values เหมือนกัน ใช้ทดสอบ equality ได้

a.hash == b.hash

น่าสนใจ: วิธีนี้เดียวกับที่เมธอด eql? ใช้:

a.eql?(b)

ใช้กับ Method objects ในตัวอย่างได้:

> a.method(:[]=).hash == a.method(:[]=).hash
=> true

สรุป

หลังคุ้นเคยกับ Ruby web development patterns มอง source optcarrot ดูว่า real-time non-web app ใช้ patterns ต่างกันอย่างไร ใน web app ผมสงสัยจะทำ array 65536 elements แต่ที่นี่ เป็นส่วนหนึ่งของ setup สำหรับ "desktop" app ทำให้มีเหตุผล

หากมีคำถามหรือความคิดเห็น ติดต่อได้ที่ starr@honeybadger.io หรือ @StarrHorne บน Twitter