Tên của kỹ thuật suy ra các đối số kiểu của một tham số loại?


9

Thiết lập: Giả sử chúng ta có một loại được gọi là Iteratorcó tham số loại Element:

interface Iterator<Element> {}

Sau đó, chúng ta có một giao diện Iterablecó một phương thức sẽ trả về một Iterator.

// T has an upper bound of Iterator
interface Iterable<T: Iterator> {
    getIterator(): T
}

Vấn đề với Iteratorviệc chung chung là chúng ta phải cung cấp cho nó các đối số kiểu.

Một ý tưởng để giải quyết điều này là "suy ra" kiểu trình vòng lặp. Mã giả sau đây thể hiện ý tưởng rằng có một biến loại Elementđược suy ra là đối số kiểu Iterator:

interface <Element> Iterable<T: Iterator<Element>> {
    getIterator(): T
}

Và sau đó chúng tôi sử dụng nó ở đâu đó như thế này:

class Vec<Element> implements Iterable<VecIterator<Element>> {/*...*/}

Định nghĩa này Iterablekhông sử dụng Elementbất cứ nơi nào khác trong định nghĩa của nó nhưng trường hợp sử dụng thực sự của tôi thì có. Một số hàm sử dụng Iterablecũng cần có khả năng ràng buộc các tham số của chúng để chấp nhận Iterables chỉ trả về một số loại trình lặp nhất định, chẳng hạn như trình lặp hai chiều, đó là lý do tại sao trình lặp được trả về được tham số hóa thay vì chỉ loại phần tử.


Câu hỏi:

  • Có một tên thành lập cho các biến loại suy ra? Những gì về kỹ thuật nói chung? Không biết danh pháp cụ thể đã gây khó khăn cho việc tìm kiếm các ví dụ này trong tự nhiên hoặc tìm hiểu về các tính năng cụ thể của ngôn ngữ.
  • Không phải tất cả các ngôn ngữ với thuốc generic đều có kỹ thuật này; Có tên cho các kỹ thuật tương tự trong các ngôn ngữ này?

1
Bạn có thể hiển thị một số mã không biên dịch mà bạn muốn biên dịch không? Tôi nghĩ rằng sẽ làm cho câu hỏi rõ ràng hơn.
Quét

1
Bạn cũng có thể chọn một ngôn ngữ (hoặc nói ngôn ngữ bạn đang sử dụng và cú pháp nghĩa là gì). Rõ ràng là không, ví dụ, C #. Có rất nhiều thông tin về "suy luận kiểu" có sẵn trên internet, nhưng tôi không chắc nó được áp dụng ở đây.

5
Tôi đang triển khai khái quát bằng một ngôn ngữ, không cố gắng lấy mã bằng bất kỳ một ngôn ngữ nào để biên dịch. Đó cũng là một câu hỏi đặt tên và thiết kế. Đây là lý do tại sao nó hơi bất khả tri. Không biết các thuật ngữ này sẽ gây khó khăn cho việc tìm các ví dụ và tài liệu bằng các ngôn ngữ hiện có. Chắc chắn đây không phải là một vấn đề độc đáo?
Levi Morrison

Câu trả lời:


2

Tôi không biết nếu có một thuật ngữ cụ thể cho vấn đề này, nhưng có ba loại giải pháp chung:

  • tránh các loại bê tông có lợi cho công văn động
  • cho phép các tham số loại giữ chỗ trong các ràng buộc kiểu
  • tránh các tham số kiểu bằng cách sử dụng các kiểu / họ liên quan

Và tất nhiên, giải pháp mặc định: tiếp tục đánh vần tất cả các tham số đó.

Tránh các loại bê tông.

Bạn đã xác định một Iterablegiao diện là:

interface <Element> Iterable<T: Iterator<Element>> {
    getIterator(): T
}

Điều này cung cấp cho người dùng sức mạnh tối đa của giao diện vì họ có được loại cụ thể chính xác Tcủa trình vòng lặp. Điều này cũng cho phép trình biên dịch áp dụng nhiều tối ưu hóa hơn như nội tuyến.

Tuy nhiên, nếu Iterator<E>là một giao diện được gửi động thì việc biết loại bê tông là không cần thiết. Đây là ví dụ giải pháp mà Java sử dụng. Giao diện sau đó sẽ được viết là:

interface Iterable<Element> {
    getIterator(): Iterator<Element>
}

Một biến thể thú vị của điều này là impl Traitcú pháp của Rust cho phép bạn khai báo hàm với kiểu trả về trừu tượng, nhưng biết rằng loại cụ thể sẽ được biết đến tại trang web cuộc gọi (do đó cho phép tối ưu hóa). Điều này hoạt động tương tự như một tham số loại ngầm.

Cho phép tham số loại giữ chỗ.

Các Iterablegiao diện không cần phải biết về các loại nguyên tố, vì vậy nó có thể là có thể viết những dòng này như sau:

interface Iterable<T: Iterator<_>> {
    getIterator(): T
}

Trường hợp T: Iterator<_>thể hiện ràng buộc thì T T là bất kỳ trình lặp nào, bất kể kiểu phần tử nào. Nghiêm khắc hơn, chúng ta có thể diễn tả điều này như: Có tồn tại một số loại Elementvì vậy đó Tlà một loại Iterator<Element>, mà không cần phải biết bất kỳ loại cụ thể nào Element. Điều này có nghĩa là biểu thức kiểu Iterator<_>không mô tả một kiểu thực tế và chỉ có thể được sử dụng như một ràng buộc kiểu.

Sử dụng loại gia đình / loại liên quan.

Ví dụ, trong C ++, một loại có thể có thành viên loại. Điều này thường được sử dụng trong toàn bộ thư viện tiêu chuẩn, ví dụ std::vector::value_type. Điều này không thực sự giải quyết vấn đề tham số loại trong tất cả các kịch bản, nhưng vì một loại có thể đề cập đến các loại khác, một tham số loại duy nhất có thể mô tả toàn bộ họ các loại liên quan.

Hãy xác định:

interface Iterator {
  type ElementType
  fn next(): ElementType
}

interface Iterable {
  type IteratorType: Iterator
  fn getIterator(): IteratorType
}

Sau đó:

class Vec<Element> implement Iterable {
  type IteratorType = VecIterator<Element>
  fn getIterator(): IteratorType { ... }
}

class VecIterator<T> implements Iterator {
  type ElementType = T
  fn next(): ElementType { ... }
}

Điều này có vẻ rất linh hoạt, nhưng lưu ý rằng điều này có thể làm cho việc thể hiện các ràng buộc kiểu khó khăn hơn. Ví dụ như bằng văn bản Iterablekhông thực thi bất kỳ loại phần tử lặp và interface Iterator<T>thay vào đó chúng tôi có thể muốn khai báo . Và bây giờ bạn đang xử lý một phép tính loại khá phức tạp. Rất dễ dàng để vô tình làm cho một hệ thống kiểu như vậy không thể giải quyết được (hoặc có thể nó đã là?).

Lưu ý rằng các loại liên quan có thể rất thuận tiện như mặc định cho các tham số loại. Ví dụ: giả sử rằng Iterablegiao diện cần một tham số loại riêng cho loại phần tử thường nhưng không phải lúc nào cũng giống với loại phần tử lặp, và chúng ta có các tham số loại giữ chỗ, có thể nói:

interface Iterable<T: Iterator<_>, Element = T::Element> {
  ...
}

Tuy nhiên, đó chỉ là một tính năng công thái học ngôn ngữ và không làm cho ngôn ngữ trở nên mạnh mẽ hơn.


Loại hệ thống rất khó, vì vậy thật tốt khi xem xét những gì không và không hoạt động trong các ngôn ngữ khác.

Ví dụ, xem xét việc đọc chương Đặc điểm nâng cao trong Sách Rust, trong đó thảo luận về các loại liên quan. Nhưng xin lưu ý rằng một số điểm có lợi cho các loại liên quan thay vì khái quát chỉ áp dụng ở đó vì ngôn ngữ không có tính năng phân nhóm và mỗi đặc điểm chỉ có thể được thực hiện tối đa một lần cho mỗi loại. Các đặc điểm của Rust không phải là các giao diện giống như Java.

Các hệ thống loại thú vị khác bao gồm Haskell với các phần mở rộng ngôn ngữ khác nhau. Các mô đun / functor OCaml là một phiên bản tương đối đơn giản của các họ kiểu, mà không trực tiếp xen kẽ chúng với các đối tượng hoặc các kiểu tham số. Java là đáng chú ý cho các hạn chế trong hệ thống loại của nó, ví dụ như thuốc generic với kiểu xóa và không có khái quát về các loại giá trị. C # rất giống Java nhưng quản lý để tránh hầu hết các hạn chế này, với chi phí tăng độ phức tạp triển khai. Scala cố gắng tích hợp các tổng quát kiểu C # với các kiểu chữ Haskell trên nền tảng Java. Các mẫu đơn giản của C ++ được nghiên cứu kỹ lưỡng nhưng không giống như hầu hết các triển khai chung chung.

Cũng đáng để xem các thư viện chuẩn của các ngôn ngữ này (đặc biệt là các bộ sưu tập thư viện tiêu chuẩn như danh sách hoặc bảng băm) để xem mẫu nào thường được sử dụng. Ví dụ, C ++ có một hệ thống phức tạp gồm các khả năng lặp khác nhau và Scala mã hóa các khả năng thu thập hạt mịn như các đặc điểm. Các giao diện thư viện tiêu chuẩn Java đôi khi không có cơ sở, ví dụ Iterator#remove(), nhưng có thể sử dụng các lớp lồng nhau như một loại kiểu liên kết (ví dụ Map.Entry).

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.