โครงสร้างที่ไม่เปลี่ยนรูปและลำดับชั้นขององค์ประกอบลึก


9

ฉันกำลังพัฒนาแอพพลิเคชั่น GUI ทำงานหนักกับกราฟิก - คุณสามารถคิดได้ว่ามันเป็นตัวแก้ไขเวกเตอร์เพื่อประโยชน์ของตัวอย่าง การดึงดูดให้โครงสร้างข้อมูลทั้งหมดไม่สามารถเปลี่ยนแปลงได้ - ดังนั้นฉันสามารถเลิกทำ / ทำซ้ำคัดลอก / วางและอื่น ๆ อีกมากมายโดยไม่ต้องใช้ความพยายาม

สำหรับประโยชน์ของความเรียบง่ายฉันจะใช้ตัวอย่างต่อไปนี้ - แอปพลิเคชันใช้ในการแก้ไขรูปหลายเหลี่ยมดังนั้นฉันจึงมีวัตถุ "รูปหลายเหลี่ยม" ซึ่งเป็นเพียงรายการของจุดที่ไม่เปลี่ยนรูป:

Scene -> Polygon -> Point

ดังนั้นฉันจึงมีตัวแปรที่เปลี่ยนแปลงไม่ได้เพียงตัวเดียวในโปรแกรมของฉัน - ตัวแปรที่เก็บวัตถุฉากปัจจุบัน ปัญหาที่ฉันเริ่มเมื่อฉันพยายามใช้การลากจุด - ในเวอร์ชันที่ไม่แน่นอนฉันก็คว้าPointวัตถุและเริ่มแก้ไขพิกัดของมัน ในเวอร์ชันที่ไม่เปลี่ยนรูป - ฉันติดอยู่ ฉันสามารถเก็บดัชนีของPolygonปัจจุบันSceneดัชนีจุดลากPolygonเข้ามาและแทนที่ทุกครั้ง แต่วิธีการนี้ไม่ได้ปรับขนาด - เมื่อระดับการเขียนเรียงกันถึง 5 และไกลออกไปแผ่นกันความร้อนก็จะทนไม่ได้

ฉันแน่ใจว่าปัญหานี้สามารถแก้ไขได้ - หลังจากทั้งหมดมี Haskell ที่มีโครงสร้างที่ไม่เปลี่ยนแปลงอย่างสมบูรณ์และ IO monad แต่ฉันไม่สามารถหาวิธีได้

คุณสามารถให้คำแนะนำแก่ฉันได้ไหม


@ งาน - นั่นคือวิธีการทำงานในขณะนี้และมันทำให้ฉันเจ็บปวดมาก ดังนั้นฉันกำลังมองหาวิธีการทางเลือก - และเปลี่ยนไม่ได้ดูเหมือนสมบูรณ์แบบสำหรับโครงสร้างโปรแกรมนี้อย่างน้อยก่อนที่เราจะเพิ่มผู้ใช้โต้ตอบกับมัน :)
Rogach

@Rogach: คุณช่วยอธิบายเพิ่มเติมเกี่ยวกับรหัสสำเร็จรูปได้ไหม?
ร. ว.

คำตอบ:


9

ฉันสามารถเก็บดัชนีของรูปหลายเหลี่ยมในซีนปัจจุบันดัชนีของจุดลากในรูปหลายเหลี่ยมและแทนที่ทุกครั้ง แต่วิธีการนี้ไม่ได้ปรับขนาด - เมื่อระดับการจัดองค์ประกอบไปถึง 5 และไกลกว่านั้น

คุณพูดถูกวิธีนี้ไม่ได้ปรับขนาดถ้าคุณไม่สามารถเข้าไปในหม้อต้มน้ำได้ โดยเฉพาะแผ่นเกราะสำหรับสร้างฉากใหม่ด้วยการเปลี่ยนแปลงส่วนย่อยเล็กน้อย อย่างไรก็ตามภาษาที่ใช้งานได้หลายตัวมีโครงสร้างสำหรับจัดการกับการจัดเรียงโครงสร้างซ้อนกันเช่นเลนส์

เลนส์นั้นเป็นผู้ทะเยอทะยานและผู้ตั้งค่าสำหรับข้อมูลที่ไม่เปลี่ยนรูป เลนส์มีความสำคัญกับส่วนเล็ก ๆ ของโครงสร้างขนาดใหญ่ รับเลนส์มีสองสิ่งที่คุณสามารถทำได้ด้วย - คุณสามารถดูส่วนเล็ก ๆ ของค่าของโครงสร้างที่มีขนาดใหญ่กว่าหรือคุณสามารถตั้งค่าส่วนเล็ก ๆ ของค่าของโครงสร้างที่มีขนาดใหญ่เป็นค่าใหม่ ตัวอย่างเช่นสมมติว่าคุณมีเลนส์ที่เน้นรายการที่สามในรายการ:

thirdItemLens :: Lens [a] a

ประเภทนั้นหมายถึงโครงสร้างที่ใหญ่กว่าคือรายการสิ่งต่าง ๆ และส่วนย่อยขนาดเล็กเป็นหนึ่งในสิ่งเหล่านั้น ด้วยเลนส์นี้คุณสามารถดูและตั้งค่ารายการที่สามในรายการ:

> view thirdItemLens [1, 2, 3, 4, 5]
3
> set thirdItemLens 100 [1, 2, 3, 4, 5]
[1, 2, 100, 4, 5]

เหตุผลที่เลนส์มีประโยชน์ก็เพราะว่ามันเป็นค่าที่เป็นตัวแทนของ getters และ setters และคุณสามารถนำไปใช้กับค่าอื่น ๆ ได้ คุณสามารถสร้างฟังก์ชั่นที่คืนเลนส์ได้เช่นlistItemLensฟังก์ชั่นที่ใช้ตัวเลขnและส่งคืนเลนส์ที่ดูnรายการที่อยู่ในรายการ นอกจากนี้ยังสามารถประกอบเลนส์:

> firstLens = listItemLens 0
> thirdLens = listItemLens 2
> firstOfThirdLens = lensCompose firstLens thirdLens
> view firstOfThirdLens [[1, 2], [3, 4], [5, 6], [7, 8]]
5
> set firstOfThirdLens 100 [[1, 2], [3, 4], [5, 6], [7, 8]]
[[1, 2], [3, 4], [100, 6], [7, 8]]

แต่ละเลนส์ห่อหุ้มพฤติกรรมสำหรับการสำรวจหนึ่งระดับของโครงสร้างข้อมูล โดยการรวมเข้าด้วยกันคุณสามารถกำจัดหม้อไอน้ำสำหรับการสำรวจโครงสร้างที่ซับซ้อนหลายระดับ ตัวอย่างเช่นสมมติว่าคุณมีscenePolygonLens iมุมมองที่เป็นiรูปหลายเหลี่ยมในฉากและpolygonPointLens nมุมมองที่เป็นnthจุดในรูปหลายเหลี่ยมคุณสามารถสร้างคอนสตรัคเลนส์เพื่อโฟกัสเฉพาะจุดที่คุณสนใจในฉากทั้งหมดดังนี้:

scenePointLens i n = lensCompose (polygonPointLens n) (scenePolygonLens i)

ตอนนี้สมมติว่าผู้ใช้คลิกจุดที่ 3 ของรูปหลายเหลี่ยม 14 แล้วเลื่อนไปทางขวา 10 พิกเซล คุณสามารถอัปเดตฉากของคุณได้เช่น:

lens = scenePointLens 14 3
point = view lens currentScene
newPoint = movePoint 10 0 point
newScene = set lens newPoint currentScene

สิ่งนี้ประกอบด้วยหม้อไอน้ำทั้งหมดสำหรับการสำรวจและอัปเดตฉากภายในlensสิ่งที่คุณต้องใส่ใจคือสิ่งที่คุณต้องการเปลี่ยนจุด คุณสามารถสรุปสิ่งนี้ได้ด้วยlensTransformฟังก์ชั่นที่รับเลนส์, เป้าหมายและฟังก์ชั่นสำหรับอัพเดทมุมมองของเป้าหมายผ่านเลนส์:

lensTransform lens transformFunc target =
  current = view lens target
  new = transformFunc current
  set lens new target

สิ่งนี้จะใช้ฟังก์ชั่นและเปลี่ยนเป็น "updater" บนโครงสร้างข้อมูลที่ซับซ้อนโดยใช้ฟังก์ชั่นเพื่อดูเฉพาะและใช้เพื่อสร้างมุมมองใหม่ ดังนั้นกลับไปที่สถานการณ์ของการเลื่อนจุดที่ 3 ของรูปหลายเหลี่ยมที่ 14 ไปทางขวา 10 พิกเซลซึ่งสามารถแสดงออกในรูปlensTransformแบบดังนี้:

lens = scenePointLens 14 3
moveRightTen point = movePoint 10 0 point
newScene = lensTransform lens moveRightTen currentScene

และนั่นคือทั้งหมดที่คุณต้องอัปเดตฉากทั้งหมด นี่เป็นความคิดที่ทรงพลังมากและทำงานได้ดีมากเมื่อคุณมีฟังก์ชั่นที่ดีในการสร้างเลนส์ที่ดูข้อมูลที่คุณสนใจ

อย่างไรก็ตามนี่เป็นสิ่งที่ค่อนข้างออกไปข้างนอกแม้ในชุมชนการเขียนโปรแกรมการทำงาน เป็นการยากที่จะหาการสนับสนุนห้องสมุดที่ดีสำหรับการทำงานกับเลนส์และยังยากที่จะอธิบายว่าพวกเขาทำงานอย่างไรและมีประโยชน์อย่างไรต่อผู้ร่วมงานของคุณ ใช้วิธีนี้ด้วยเม็ดเกลือ


คำอธิบายที่ยอดเยี่ยม! ตอนนี้ฉันได้สิ่งที่เป็นเลนส์!
Vincent Lecrubier

13

ฉันทำงานกับปัญหาเดียวกันทุกประการ (แต่มีเพียง 3 องค์ประกอบเท่านั้น) ความคิดพื้นฐานคือการโคลนแล้วปรับเปลี่ยน ในรูปแบบการเขียนโปรแกรมไม่เปลี่ยนรูปโคลนและการเปลี่ยนแปลงที่เกิดขึ้นร่วมกันซึ่งจะกลายเป็นคำสั่งวัตถุ

โปรดทราบว่าในรูปแบบการเขียนโปรแกรมที่ไม่แน่นอนการโคลนจะมีความจำเป็นอยู่แล้ว:

  • หากต้องการอนุญาตเลิกทำ / ทำซ้ำ
  • ระบบการแสดงผลอาจต้องแสดงรูปแบบ "ก่อนแก้ไข" และ "ระหว่างการแก้ไข" พร้อมกันซ้อนทับกัน (เป็นเส้นโกสต์) เพื่อให้ผู้ใช้สามารถเห็นการเปลี่ยนแปลง

ในรูปแบบการเขียนโปรแกรมที่ไม่แน่นอน

  • โครงสร้างที่มีอยู่ถูกโคลนแบบลึก
  • การเปลี่ยนแปลงจะทำในสำเนาโคลน
  • เอ็นจิ้นการแสดงผลได้รับการบอกกล่าวให้แสดงโครงสร้างเก่าเป็นเส้นผีและโครงสร้างโคลน / ปรับเปลี่ยนเป็นสี

ในรูปแบบการเขียนโปรแกรมที่ไม่เปลี่ยนรูป

  • การกระทำของผู้ใช้แต่ละครั้งที่ส่งผลให้เกิดการปรับเปลี่ยนข้อมูลจะถูกแมปกับลำดับของ "คำสั่ง"
  • วัตถุคำสั่งห่อหุ้มสิ่งที่การปรับเปลี่ยนที่จะใช้และการอ้างอิงถึงโครงสร้างดั้งเดิม
    • ในกรณีของฉันวัตถุคำสั่งของฉันจะจดจำดัชนีจุดที่ต้องเปลี่ยนและพิกัดใหม่เท่านั้น (เช่นน้ำหนักเบามากเนื่องจากฉันไม่ได้ติดตามสไตล์ที่ไม่เปลี่ยนรูปอย่างเคร่งครัด)
  • เมื่อคำสั่งวัตถุถูกดำเนินการมันจะสร้างสำเนาลึกของโครงสร้างทำให้การปรับเปลี่ยนถาวรในสำเนาใหม่
  • เมื่อผู้ใช้ทำการแก้ไขเพิ่มเติมวัตถุคำสั่งเพิ่มเติมจะถูกสร้างขึ้น

1
ทำไมต้องทำสำเนาลึกของโครงสร้างข้อมูลที่ไม่เปลี่ยนรูป? คุณเพียงแค่ต้องคัดลอก "กระดูกสันหลัง" ของการอ้างอิงจากวัตถุที่ถูกปรับเปลี่ยนไปยังรูทและเก็บการอ้างอิงไปยังส่วนที่เหลือของโครงสร้างเดิม
Reinstate Monica

3

วัตถุที่ไม่เปลี่ยนแปลงอย่างลึกล้ำนั้นมีข้อได้เปรียบที่การโคลนนิ่งบางอย่างจำเป็นต้องคัดลอกข้อมูลอ้างอิง พวกเขามีข้อเสียที่ทำให้แม้แต่การเปลี่ยนแปลงเล็ก ๆ น้อย ๆ กับวัตถุที่ซ้อนกันอย่างลึก ๆ ต้องสร้างตัวอย่างใหม่ของทุกวัตถุที่มันซ้อนอยู่ วัตถุที่เปลี่ยนแปลงได้มีความได้เปรียบที่การเปลี่ยนวัตถุนั้นง่าย - เพียงแค่ทำมัน - แต่การโคลนนิ่งลึก ๆ วัตถุนั้นต้องการการสร้างวัตถุใหม่ซึ่งมีการโคลนแบบลึกของวัตถุที่ซ้อนกันทุกตัว ที่แย่กว่านั้นหากใครต้องการโคลนวัตถุและทำการเปลี่ยนแปลงโคลนวัตถุนั้นทำการเปลี่ยนแปลงอื่น ฯลฯ จากนั้นไม่ว่าการเปลี่ยนแปลงเล็กหรือใหญ่จะต้องเก็บสำเนาของลำดับชั้นทั้งหมดสำหรับทุกรุ่นที่บันทึกไว้ของ สถานะของวัตถุ น่ารังเกียจ

วิธีที่อาจมีมูลค่าการพิจารณาคือการกำหนดประเภท "อาจจะมีชื่อเสียง" นามธรรมกับประเภทอนุพันธ์ที่เปลี่ยนแปลงไม่ได้และลึกล้ำ ทุกประเภทดังกล่าวจะมีAsImmutableวิธีการ; การเรียกใช้เมธอดนั้นบนอินสแตนซ์ที่ไม่สามารถเปลี่ยนได้ของวัตถุจะส่งคืนอินสแตนซ์นั้น การเรียกมันบนอินสแตนซ์ที่เปลี่ยนแปลงได้จะส่งคืนอินสแตนซ์ที่ไม่เปลี่ยนรูปที่มีคุณสมบัติเป็นสแน๊ปช็อตที่ไม่เปลี่ยนรูปแบบของการเทียบเท่าในแบบดั้งเดิม ประเภทที่เปลี่ยนรูปไม่ได้ด้วยการเทียบเท่าที่ไม่แน่นอนจะกีฬาAsMutableวิธีการซึ่งจะสร้างอินสแตนซ์ที่ไม่แน่นอนที่มีคุณสมบัติตรงกับที่ของเดิม

การเปลี่ยนวัตถุที่ซ้อนกันในวัตถุที่เปลี่ยนแปลงไม่ได้อย่างล้ำลึกนั้นจะต้องเปลี่ยนวัตถุที่ไม่เปลี่ยนรูปแบบด้านนอกด้วยวัตถุที่เปลี่ยนแปลงไม่ได้ก่อนแล้วจึงเปลี่ยนคุณสมบัติที่มีสิ่งที่จะเปลี่ยนด้วยสิ่งที่เปลี่ยนแปลงไม่ได้ ฯลฯ แต่ทำการเปลี่ยนแปลงซ้ำ ๆ วัตถุโดยรวมจะไม่ต้องการทำให้วัตถุเพิ่มเติมใด ๆ จนกว่าจะถึงเวลาเช่นความพยายามที่จะเรียกAsImmutableวัตถุที่ไม่แน่นอน (ซึ่งจะทำให้วัตถุที่ไม่แน่นอนไม่แน่นอน แต่กลับวัตถุที่เปลี่ยนแปลงไม่ได้ถือข้อมูลเดียวกัน)

การปรับให้เรียบง่าย แต่มีนัยสำคัญวัตถุที่ไม่แน่นอนแต่ละตัวสามารถเก็บการอ้างอิงแคชกับวัตถุประเภทที่ไม่เปลี่ยนรูปแบบที่เกี่ยวข้องและแต่ละประเภทไม่เปลี่ยนรูปควรแคชGetHashCodeค่าของมัน เมื่อเรียกAsImmutableใช้วัตถุที่ไม่แน่นอนก่อนส่งคืนวัตถุที่เปลี่ยนรูปไม่ได้ใหม่ให้ตรวจสอบว่ามันจะตรงกับการอ้างอิงแคช หากเป็นเช่นนั้นให้ส่งคืนการอ้างอิงที่แคช (ละทิ้งวัตถุที่ไม่เปลี่ยนรูปแบบใหม่) มิฉะนั้นอัปเดตการอ้างอิงแคชเพื่อเก็บวัตถุใหม่และส่งคืนนั้น หากทำเสร็จแล้วให้โทรซ้ำอีกครั้งAsImmutableโดยไม่มีการกลายพันธุ์ที่แทรกแซงใด ๆ จะให้ผลการอ้างอิงวัตถุเดียวกัน แม้ว่าจะไม่ได้ประหยัดค่าใช้จ่ายในการสร้างอินสแตนซ์ใหม่ แต่ก็จะหลีกเลี่ยงค่าใช้จ่ายในการเก็บรักษาหน่วยความจำ นอกจากนี้การเปรียบเทียบความเท่าเทียมกันระหว่างวัตถุที่ไม่เปลี่ยนรูปอาจเร่งด่วนมากหากในกรณีส่วนใหญ่รายการที่ถูกเปรียบเทียบนั้นมีค่าอ้างอิงเท่ากันหรือมีรหัสแฮชต่างกัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.