มีจุดประสงค์เฉพาะสำหรับรายการต่างกันหรือไม่?


13

มาจากพื้นหลัง C # และ Java ฉันคุ้นเคยกับรายการของฉันที่เป็นเนื้อเดียวกันและนั่นสมเหตุสมผลสำหรับฉัน เมื่อฉันเริ่มยก Lisp ฉันสังเกตเห็นว่ารายการต่างกัน เมื่อฉันเริ่มที่จะวนรอบด้วยdynamicคำสำคัญใน C # ฉันสังเกตว่าใน C # 4.0 อาจมีรายการที่ต่างกันเช่นกัน:

List<dynamic> heterogeneousList

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


1
คุณหมายถึง ... ฉันสังเกตเห็นว่ารายการต่างกันไปหรือไม่ ... ?
วิศวกรโลก

เป็นวิธีการที่List<dynamic>แตกต่างกัน (สำหรับคำถามของคุณ) จากเพียงแค่การทำList<object>?
Peter K.

@WorldEngineer ใช่ฉันทำ ฉันได้อัปเดตโพสต์แล้ว ขอบคุณ!
Jetti

@PeterK ฉันเดาว่าใช้ทุกวันไม่มีความแตกต่าง อย่างไรก็ตามไม่ใช่ทุกประเภทใน C # ที่ได้มาจาก System.Objectดังนั้นจะมีกรณีขอบที่มีความแตกต่าง
Jetti

คำตอบ:


16

กระดาษที่มีการพิมพ์แบบ Heterogenous Collectionsโดย Oleg Klysov, Ralf Lämmelและ Keean Schupke ไม่เพียง แต่มีการใช้งานรายการ heterogenous ใน Haskell เท่านั้น แต่ยังเป็นตัวอย่างที่สร้างแรงบันดาลใจให้คุณเมื่อใช้ทำไม HLists โดยเฉพาะอย่างยิ่งพวกเขาใช้มันสำหรับการเข้าถึงฐานข้อมูลแบบตรวจสอบเวลารวบรวมอย่างปลอดภัย (คิดว่า LINQ จริงๆแล้วกระดาษที่พวกเขาอ้างถึงคือกระดาษ Haskell โดย Erik Meijer และคณะที่นำไปสู่ ​​LINQ)

การอ้างอิงจากย่อหน้าเบื้องต้นของกระดาษ HLists:

นี่คือรายการตัวอย่างทั่วไปแบบเปิดที่เรียกใช้คอลเลกชันที่แตกต่างกัน:

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

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


ขอบคุณสำหรับลิงค์และคำตอบของคุณ คุณได้จุดที่ดีว่ารายการนั้นไม่เหมือนกันจริง ๆ แต่พิมพ์อย่างอ่อน ฉันรอคอยที่จะอ่านกระดาษนั้น (อาจจะในวันพรุ่งนี้คืนนี้ฉันได้กลางภาค :))
Jetti

5

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

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

สัญชาตญาณของคุณเกี่ยวกับการเลือกโครงสร้างข้อมูลที่ดีกว่านั้นตรงประเด็น โดยทั่วไปสัญญาที่คุณสามารถใส่ในรหัสของคุณยิ่งคุณรู้มากขึ้นเกี่ยวกับวิธีการทำงานและยิ่งเชื่อถือได้บำรุงรักษา & c มันกลายเป็น. แต่บางครั้งคุณจริงๆไม่ต้องการภาชนะที่แตกต่างกันและคุณควรจะได้รับอนุญาตให้มีหนึ่งถ้าคุณต้องการมัน


1
“ อย่างไรก็ตามบางครั้งคุณต้องการภาชนะที่แตกต่างกันจริงๆและคุณควรได้รับอนุญาตถ้ามีมัน” - ทำไมล่ะ? นั่นคือคำถามของฉัน ทำไมคุณถึงต้องรวมข้อมูลเข้าไว้ในรายการแบบสุ่ม?
Jetti

@Jetti: สมมติว่าคุณมีรายการการตั้งค่าต่าง ๆ ที่ผู้ใช้ป้อน คุณสามารถสร้างอินเทอร์เฟซIUserSettingและนำไปใช้หลาย ๆ ครั้งหรือสร้างแบบทั่วไปUserSetting<T>แต่หนึ่งในปัญหาของการพิมพ์แบบสแตติกคือคุณกำลังกำหนดอินเทอร์เฟซก่อนที่คุณจะรู้ได้อย่างแม่นยำว่าจะใช้งานอย่างไร สิ่งที่คุณทำกับการตั้งค่าจำนวนเต็มอาจแตกต่างจากสิ่งที่คุณทำกับการตั้งค่าสตริงดังนั้นการดำเนินการใดที่เหมาะสมในอินเทอร์เฟซทั่วไป จนกว่าคุณจะทราบแน่ชัดว่าควรใช้การพิมพ์แบบไดนามิกอย่างรอบคอบและทำให้เป็นรูปธรรมในภายหลัง
Jon Purdy

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

@Jetti: นั่นเป็นปัญหาเดียวกันโดยพื้นฐาน - คลาสฐานสากลไม่ควรมีอยู่ตั้งแต่แรกเพราะไม่ว่าการดำเนินการที่กำหนดไว้จะมีประเภทใดที่การดำเนินการเหล่านั้นไม่สมเหตุสมผล แต่ถ้า C # ช่วยให้ใช้งานได้ง่ายobjectกว่า a dynamicให้ใช้ตัวแรก
Jon Purdy

1
@Jetti: นี่คือสิ่งที่มีความแตกต่างเกี่ยวกับ รายการประกอบด้วยวัตถุ "heterogenous" จำนวนหนึ่งถึงแม้ว่ามันจะเป็นคลาสย่อยของซูเปอร์คลาสเดียวก็ตาม จากมุมมอง Java คุณสามารถเรียกข้อกำหนดคลาส (หรืออินเทอร์เฟซ) ได้ สำหรับภาษาอื่น ๆ (LISP, Python และอื่น ๆ ) ไม่มีประโยชน์อะไรที่จะทำให้การประกาศทั้งหมดถูกต้องเนื่องจากไม่มีความแตกต่างในการใช้งานจริง
S.Lott

2

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

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

public class ListVisitor
{
  public void DoSomething(IEnumerable<dynamic> list)
  {
    foreach(dynamic obj in list)
    {
       DoSomething(obj);
    }
  }

  public void DoSomething(SomeClass obj)
  {
    //do something with SomeClass
  }

  public void DoSomething(AnotherClass obj)
  {
    //do something with AnotherClass
  }

  public void DoSomething(Object obj)
  {
    //do something with everything els
  }
}

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

ด้านพลิกคือการแจ้งให้ทุกคนที่ลงทะเบียนข้อความ (ตัวอย่างเช่นรูปแบบ Event Aggregator ที่ใช้สำหรับการเชื่อมต่อแบบหลวมของ ViewModels ในรูปแบบ MVVM) ฉันใช้โครงสร้างต่อไปนี้

IDictionary<Type, List<Object>>

วิธีเดียวที่จะเพิ่มในพจนานุกรมคือฟังก์ชั่น

Register<T>(Action<T> handler)

(และวัตถุเป็นจริง WeakReference เพื่อส่งผ่านในการจัดการ) ดังนั้นที่นี่ฉันต้องใช้ List <Object> เพราะในเวลารวบรวมฉันไม่รู้ว่าประเภทปิดจะเป็นอย่างไร ที่ Runtime แต่ฉันสามารถบังคับให้เป็นประเภทนั้นซึ่งเป็นกุญแจสำคัญสำหรับพจนานุกรม เมื่อฉันต้องการไฟเหตุการณ์ที่ฉันเรียก

Send<T>(T message)

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


ด้วยการจับคู่รูปแบบกรณีและปัญหา (โดยปกติคืออย่างน้อยใน ML และ Haskell) ที่กำหนดไว้ในรูปแบบหินโดยการประกาศประเภทข้อมูลที่ตรงกัน รายการที่มีประเภทดังกล่าวไม่เหมือนกันทั้งคู่

ฉันไม่แน่ใจเกี่ยวกับ ML และ Haskell แต่ Erlang สามารถจับคู่ใด ๆ ซึ่งหมายความว่าถ้าคุณมาถึงที่นี่เพราะไม่พอใจการแข่งขันอื่น ๆ ให้ทำเช่นนี้
Michael Brown

@MikeBrown - นี่เป็นสิ่งที่ดี แต่ไม่ครอบคลุมว่าทำไมคนเราถึงใช้รายการที่แตกต่างกันและจะไม่ทำงานกับรายการ <dynamic>
เสมอ

4
ใน C #, overloads ได้รับการแก้ไขที่รวบรวมเวลา ดังนั้นตัวอย่างรหัสของคุณจะเสมอโทรDoSomething(Object)(อย่างน้อยเมื่อใช้objectในforeachวง; dynamicเป็นสิ่งที่แตกต่างกันทั้งหมด)
Heinzi

@Heinzi คุณพูดถูก ... ฉันง่วงนอนวันนี้: P คงที่
Michael Brown

0

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

จากประสบการณ์ของฉันการจัดการกับข้อมูลดิบผ่านไฟล์ซ็อกเก็ตเครือข่าย ฯลฯ คุณมักประสบปัญหาดังกล่าว

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

บันทึกเหล่านี้จะถูกเก็บไว้ที่ไหน? โดยสังเขปสิ่งที่คุณต้องการคือสิ่งที่คล้ายDictionary<WorkId, Future<TValue>>กัน แต่นี่เป็นการ จำกัด ให้คุณจัดการฟิวเจอร์สเพียงประเภทเดียวในระบบทั้งหมด ประเภทที่เหมาะสมกว่าคือDictionary<WorkId, Future<dynamic>>เนื่องจากคนงานสามารถส่งไปยังประเภทที่เหมาะสมเมื่อมีแรงในอนาคต

หมายเหตุ : ตัวอย่างนี้มาจากโลก Haskell ที่เราไม่มี subtyping ฉันจะไม่แปลกใจถ้ามีวิธีแก้ปัญหาที่เป็นสำนวนมากขึ้นสำหรับตัวอย่างนี้ใน C # แต่ก็หวังว่าจะเป็นตัวอย่าง


0

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

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