Có một mục đích cụ thể cho danh sách không đồng nhất?


13

Đến từ nền tảng C # và Java, tôi đã quen với các danh sách của mình là đồng nhất và điều đó có ý nghĩa với tôi. Khi tôi bắt đầu chọn Lisp, tôi nhận thấy rằng các danh sách có thể không đồng nhất. Khi tôi bắt đầu xoay quanh dynamictừ khóa trong C #, tôi nhận thấy rằng, kể từ C # 4.0, cũng có thể có các danh sách không đồng nhất:

List<dynamic> heterogeneousList

Câu hỏi của tôi là điểm là gì? Có vẻ như một danh sách không đồng nhất sẽ có nhiều chi phí hơn khi thực hiện xử lý và nếu bạn cần lưu trữ các loại khác nhau ở một nơi, bạn có thể cần một cấu trúc dữ liệu khác. Là sự ngây thơ của tôi nuôi dưỡng khuôn mặt xấu xí của nó hay có những lúc thực sự hữu ích khi có một danh sách không đồng nhất?


1
Ý bạn là ... tôi nhận thấy rằng các danh sách có thể không đồng nhất ...?
Kỹ sư thế giới

Làm thế nào là List<dynamic>khác nhau (cho câu hỏi của bạn) từ chỉ đơn giản là làm List<object>?
Peter K.

@WorldEngineer vâng, tôi có. Tôi đã cập nhật bài viết của mình. Cảm ơn!
Jetti

@PeterK. Tôi đoán để sử dụng hàng ngày, không có sự khác biệt. Tuy nhiên, không phải mọi loại trong C # đều xuất phát từ System.Object nên sẽ có trường hợp cạnh có sự khác biệt.
Jetti

Câu trả lời:


16

Bài viết Bộ sưu tập không đồng nhất được gõ mạnh bởi Oleg Kiselyov, Ralf Lämmel và Keean Schupke không chỉ chứa đựng một danh sách dị thể trong Haskell, mà còn là một ví dụ thúc đẩy về thời điểm, lý do và cách bạn sẽ sử dụng HLists. Cụ thể, họ đang sử dụng nó để truy cập cơ sở dữ liệu kiểm tra thời gian biên dịch an toàn kiểu. (Hãy nghĩ rằng LINQ, trên thực tế, bài báo mà họ đang tham khảo bài báo Haskell của Erik Meijer et al dẫn đến LINQ.)

Trích dẫn từ đoạn giới thiệu của bài viết của HLists:

Dưới đây là danh sách kết thúc mở của các ví dụ điển hình yêu cầu các bộ sưu tập không đồng nhất:

  • Một bảng biểu tượng được cho là lưu trữ các mục thuộc các loại khác nhau là không đồng nhất. Nó là một bản đồ hữu hạn, trong đó loại kết quả phụ thuộc vào giá trị đối số.
  • Một phần tử XML được gõ không đồng nhất. Trong thực tế, các phần tử XML là các bộ sưu tập lồng nhau bị ràng buộc bởi các biểu thức chính quy và thuộc tính mơ hồ 1.
  • Mỗi hàng được trả về bởi một truy vấn SQL là một bản đồ không đồng nhất từ ​​tên cột đến các ô. Kết quả của một truy vấn là một dòng đồng nhất của các hàng không đồng nhất.
  • Việc thêm một hệ thống đối tượng nâng cao vào một ngôn ngữ chức năng đòi hỏi các bộ sưu tập không đồng nhất của một loại kết hợp các bản ghi mở rộng với phân nhóm và giao diện liệt kê.

Lưu ý rằng các ví dụ bạn đưa ra trong câu hỏi của bạn thực sự không phải là danh sách không đồng nhất theo nghĩa là từ này thường được sử dụng. Họ đang yếu gõ hoặc không định kiểu liệt kê. Trên thực tế, chúng thực sự là các danh sách đồng nhất , vì tất cả các yếu tố đều cùng loại: objecthoặc dynamic. Sau đó, bạn bị buộc phải thực hiện các diễn viên hoặc instanceofcác bài kiểm tra không được kiểm tra hoặc một cái gì đó tương tự, để thực sự có thể làm việc một cách có ý nghĩa với các yếu tố, khiến chúng bị gõ yếu.


Cảm ơn các liên kết và phản ứng của bạn. Bạn đưa ra một điểm tốt là các danh sách thực sự không đồng nhất nhưng được đánh máy yếu. Tôi mong được đọc bài báo đó (có thể là vào ngày mai, đã cho tôi một bài kiểm tra giữa kỳ tối nay :))
Jetti

5

Câu chuyện dài, container không đồng nhất trao đổi hiệu năng thời gian chạy cho linh hoạt. Nếu bạn muốn có một danh sách các công cụ khác mà không liên quan đến các loại công cụ cụ thể, thì sự không đồng nhất là cách tốt nhất. Lisps được gõ động đặc trưng, ​​và hầu hết mọi thứ đều là một danh sách khuyết điểm của các giá trị được đóng hộp, vì vậy, hiệu suất nhỏ được mong đợi. Trong thế giới Lisp, năng suất lập trình viên được coi là quan trọng hơn hiệu năng thời gian chạy.

Trong một ngôn ngữ được gõ động, các thùng chứa đồng nhất thực sự sẽ có một chi phí không đáng kể so với các ngôn ngữ không đồng nhất, bởi vì tất cả các yếu tố được thêm vào sẽ cần phải được kiểm tra loại.

Trực giác của bạn về việc chọn một cấu trúc dữ liệu tốt hơn là điểm chính. Nói chung, càng nhiều hợp đồng bạn có thể đặt vào mã của mình, bạn càng biết nhiều về cách thức hoạt động của nó và càng đáng tin cậy, có thể duy trì, & c. no trở nên. Tuy nhiên, đôi khi bạn thực sự muốn có một thùng chứa không đồng nhất, và bạn nên được phép có một cái nếu bạn cần nó.


1
"Tuy nhiên, đôi khi bạn thực sự muốn có một thùng chứa không đồng nhất, và bạn nên được phép có một cái nếu bạn cần nó." - Tại sao mặc dù? Đó là câu hỏi của tôi. Tại sao bạn chỉ cần hodgepodge một loạt dữ liệu vào một danh sách ngẫu nhiên?
Jetti

@Jetti: Giả sử bạn có một danh sách các cài đặt do người dùng nhập vào. Bạn có thể tạo giao diện IUserSettingvà thực hiện giao diện nhiều lần hoặc tạo một giao diện chung UserSetting<T>, nhưng một trong những vấn đề với gõ tĩnh là bạn đang xác định giao diện trước khi bạn biết chính xác cách sử dụng giao diện đó. Những điều bạn làm với cài đặt số nguyên có thể rất khác so với những gì bạn làm với cài đặt chuỗi, vậy thao tác nào có ý nghĩa để đặt trong giao diện chung? Cho đến khi bạn biết chắc chắn, tốt hơn hết là sử dụng kiểu gõ động một cách thận trọng và làm cho nó cụ thể hơn sau này.
Jon Purdy

Xem đó là nơi tôi gặp vấn đề. Đối với tôi, đó dường như là thiết kế tồi, tạo ra thứ gì đó trước khi bạn biết nó sẽ làm gì / được sử dụng. Ngoài ra, trong trường hợp đó, bạn có thể tạo Giao diện với giá trị trả về của đối tượng. Có giống như danh sách không đồng nhất nhưng rõ ràng hơn và dễ sửa hơn một khi bạn biết chắc chắn các loại được sử dụng trong giao diện là gì.
Jetti

@Jetti: Về cơ bản đó là cùng một vấn đề, mặc dù, một lớp cơ sở phổ quát không nên tồn tại ở nơi đầu tiên bởi vì, bất kể hoạt động nào được định nghĩa, sẽ có một loại mà các hoạt động đó không có ý nghĩa. Nhưng nếu C # làm cho nó dễ sử dụng objecthơn là a dynamic, thì chắc chắn, hãy sử dụng cái trước.
Jon Purdy

1
@Jetti: Đây là những gì đa hình là về. Danh sách này chứa một số đối tượng "không đồng nhất" mặc dù chúng có thể là các lớp con của một siêu lớp đơn. Từ quan điểm Java, bạn có thể hiểu đúng các định nghĩa lớp (hoặc giao diện). Đối với các ngôn ngữ khác (LISP, Python, v.v.) không có lợi ích gì để có được tất cả các khai báo đúng, vì không có sự khác biệt thực thi thực tế.
S.Lott

2

Trong các ngôn ngữ chức năng (như lisp), bạn sử dụng khớp mẫu để xác định điều gì xảy ra với một thành phần cụ thể trong danh sách. Tương đương trong C # sẽ là một chuỗi các câu lệnh if ... otherif kiểm tra loại phần tử và thực hiện thao tác dựa trên đó. Không cần phải nói, khớp mẫu chức năng hiệu quả hơn so với kiểm tra kiểu thời gian chạy.

Sử dụng đa hình sẽ là một kết hợp gần hơn với khớp mẫu. Nghĩa là, có các đối tượng của danh sách khớp với một giao diện cụ thể và gọi một hàm trên giao diện đó cho từng đối tượng. Một cách khác là cung cấp một loạt các phương thức quá tải, lấy một loại đối tượng cụ thể làm tham số. Phương thức mặc định lấy Object làm tham số của nó.

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
  }
}

Cách tiếp cận này cung cấp một xấp xỉ để khớp mẫu Lisp. Mẫu khách truy cập (như được triển khai ở đây, là một ví dụ tuyệt vời về việc sử dụng cho các danh sách không đồng nhất). Một ví dụ khác là gửi tin nhắn trong đó, có người nghe cho một số tin nhắn trong hàng đợi ưu tiên và sử dụng chuỗi trách nhiệm, người điều phối chuyển tin nhắn và trình xử lý đầu tiên khớp với tin nhắn xử lý nó.

Mặt trái là thông báo cho tất cả những người đăng ký tin nhắn (ví dụ: mẫu Tổng hợp sự kiện thường được sử dụng để ghép lỏng ViewModels trong mẫu MVVM). Tôi sử dụng cấu trúc sau

IDictionary<Type, List<Object>>

Cách duy nhất để thêm vào từ điển là một chức năng

Register<T>(Action<T> handler)

(và đối tượng thực sự là một WeakReference cho người được truyền trong handler). Vì vậy, ở đây tôi PHẢI sử dụng Danh sách <Object> vì tại thời điểm biên dịch, tôi không biết kiểu đóng sẽ là gì. Tuy nhiên, tại Runtime tôi có thể thực thi rằng Loại đó sẽ là chìa khóa cho từ điển. Khi tôi muốn khởi động sự kiện, tôi gọi

Send<T>(T message)

và một lần nữa tôi giải quyết danh sách. Không có lợi thế nào khi sử dụng Danh sách <động> vì dù sao tôi cũng cần truyền nó. Vì vậy, như bạn thấy có những giá trị cho cả hai phương pháp. Nếu bạn định tự động gửi một đối tượng bằng cách sử dụng Phương thức nạp chồng, thì động là cách để thực hiện. Nếu bạn bị ép buộc bỏ bất kể, cũng có thể sử dụng Object.


Với khớp mẫu, các trường hợp (thường - ít nhất là bằng ML và Haskell) được đặt trong đá bằng cách khai báo kiểu dữ liệu khớp với. Danh sách chứa các loại như vậy không phải là không đồng nhất.

Tôi không chắc chắn về ML và Haskell nhưng Erlang có thể phù hợp với bất kỳ phương tiện nào, nếu bạn đã đến đây vì không có trận đấu nào khác được thỏa mãn, hãy làm điều này.
Michael Brown

@MikeBrown - Điều này thật tuyệt nhưng không bao gồm TẠI SAO người ta sẽ sử dụng danh sách không đồng nhất và không phải lúc nào cũng hoạt động với Danh sách <động>
Jetti

4
Trong C #, quá tải được giải quyết tại thời điểm biên dịch . Do đó, mã ví dụ của bạn sẽ luôn gọi DoSomething(Object)(ít nhất là khi sử dụng objecttrong foreachvòng lặp; dynamiclà một điều hoàn toàn khác).
Heinzi

@Heinzi, bạn nói đúng ... Hôm nay tôi buồn ngủ: P đã sửa
Michael Brown

0

Bạn đã đúng rằng tính không đồng nhất mang chi phí thời gian chạy, nhưng quan trọng hơn là nó làm suy yếu các đảm bảo thời gian biên dịch được cung cấp bởi bộ đánh máy. Mặc dù vậy, có một số vấn đề trong đó các giải pháp thay thế thậm chí còn tốn kém hơn.

Theo kinh nghiệm của tôi, xử lý các byte thô thông qua các tệp, ổ cắm mạng, v.v., bạn thường gặp phải các vấn đề như vậy.

Để đưa ra một ví dụ thực tế, hãy xem xét một hệ thống cho tính toán phân tán bằng cách sử dụng tương lai . Một công nhân trên một nút riêng lẻ có thể sinh ra công việc thuộc bất kỳ loại tuần tự hóa nào, mang lại một tương lai của loại đó. Đằng sau hậu trường, hệ thống sẽ gửi công việc cho một người ngang hàng, và sau đó lưu một bản ghi liên kết đơn vị công việc đó với tương lai cụ thể phải được điền vào khi câu trả lời cho công việc đó trở lại.

Những hồ sơ này có thể được lưu giữ ở đâu? Theo trực giác, những gì bạn muốn là một cái gì đó giống như một Dictionary<WorkId, Future<TValue>>, nhưng điều này giới hạn bạn chỉ quản lý một loại tương lai trong toàn bộ hệ thống. Loại phù hợp hơn là Dictionary<WorkId, Future<dynamic>>, vì công nhân có thể chuyển sang loại thích hợp khi nó buộc tương lai.

Lưu ý : Ví dụ này xuất phát từ thế giới Haskell nơi chúng ta không có phân nhóm. Tôi sẽ không ngạc nhiên nếu có một giải pháp thành ngữ hơn cho ví dụ cụ thể này trong C #, nhưng hy vọng nó vẫn mang tính minh họa.


0

ISTR rằng Lisp không có bất kỳ cấu trúc dữ liệu nào ngoài danh sách, vì vậy nếu bạn cần bất kỳ loại đối tượng dữ liệu tổng hợp nào, thì đó sẽ phải là một danh sách không đồng nhất. Như những người khác đã chỉ ra, chúng cũng hữu ích cho việc tuần tự hóa dữ liệu để truyền hoặc lưu trữ. Một tính năng hay là chúng cũng có kết thúc mở, vì vậy bạn có thể sử dụng chúng trong một hệ thống dựa trên sự tương tự giữa ống và bộ lọc và có các bước xử lý liên tiếp để tăng hoặc sửa dữ liệu mà không yêu cầu đối tượng dữ liệu cố định hoặc cấu trúc liên kết dòng công việc .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.