Tôi đọc qua bài viết Wikipedia Các loại hiện sinh . Tôi tập hợp rằng chúng được gọi là các kiểu tồn tại vì toán tử tồn tại (). Tôi không chắc ý của nó là gì. Sự khác biệt giữa
T = ∃X { X a; int f(X); }
và
T = ∀x { X a; int f(X); }
?
Tôi đọc qua bài viết Wikipedia Các loại hiện sinh . Tôi tập hợp rằng chúng được gọi là các kiểu tồn tại vì toán tử tồn tại (). Tôi không chắc ý của nó là gì. Sự khác biệt giữa
T = ∃X { X a; int f(X); }
và
T = ∀x { X a; int f(X); }
?
Câu trả lời:
Khi ai đó định nghĩa một loại phổ quát ∀X
mà họ đang nói: Bạn có thể cắm bất kỳ loại nào bạn muốn, tôi không cần biết bất cứ điều gì về loại công việc của mình, tôi sẽ chỉ đề cập đến nó một cách ngẫu nhiên nhưX
.
Khi ai đó định nghĩa một loại tồn tại ∃X
họ đang nói: Tôi sẽ sử dụng bất kỳ loại nào tôi muốn ở đây; bạn sẽ không biết gì về loại này, vì vậy bạn chỉ có thể tham khảo nó một cách rõ ràng nhưX
.
Các loại phổ quát cho phép bạn viết những thứ như:
void copy<T>(List<T> source, List<T> dest) {
...
}
Các copy
chức năng không có ý tưởng những gì T
thực sự sẽ được, nhưng nó không cần.
Các kiểu hiện sinh sẽ cho phép bạn viết những thứ như:
interface VirtualMachine<B> {
B compile(String source);
void run(B bytecode);
}
// Now, if you had a list of VMs you wanted to run on the same input:
void runAllCompilers(List<∃B:VirtualMachine<B>> vms, String source) {
for (∃B:VirtualMachine<B> vm : vms) {
B bytecode = vm.compile(source);
vm.run(bytecode);
}
}
Mỗi triển khai máy ảo trong danh sách có thể có một loại mã byte khác nhau. Các runAllCompilers
chức năng không có ý tưởng gì loại bytecode là, nhưng nó không cần phải; tất cả những gì nó làm là chuyển tiếp mã byte từ VirtualMachine.compile
đến VirtualMachine.run
.
Các ký tự đại diện kiểu Java (ví dụ List<?>
:) là một dạng rất hạn chế của các kiểu tồn tại.
Cập nhật: Quên đề cập rằng bạn có thể sắp xếp mô phỏng các loại tồn tại với các loại phổ quát. Đầu tiên, bọc loại phổ quát của bạn để ẩn tham số loại. Thứ hai, điều khiển đảo ngược (điều này có hiệu quả hoán đổi phần "bạn" và "tôi" trong các định nghĩa ở trên, đây là điểm khác biệt chính giữa các tồn tại và phổ quát).
// A wrapper that hides the type parameter 'B'
interface VMWrapper {
void unwrap(VMHandler handler);
}
// A callback (control inversion)
interface VMHandler {
<B> void handle(VirtualMachine<B> vm);
}
Bây giờ chúng ta có thể có VMWrapper
cuộc gọi của riêng chúng ta VMHandler
có handle
chức năng đánh máy phổ quát . Hiệu ứng ròng là như nhau, mã của chúng tôi phải coi B
là mờ đục.
void runWithAll(List<VMWrapper> vms, final String input)
{
for (VMWrapper vm : vms) {
vm.unwrap(new VMHandler() {
public <B> void handle(VirtualMachine<B> vm) {
B bytecode = vm.compile(input);
vm.run(bytecode);
}
});
}
}
Một ví dụ về triển khai VM:
class MyVM implements VirtualMachine<byte[]>, VMWrapper {
public byte[] compile(String input) {
return null; // TODO: somehow compile the input
}
public void run(byte[] bytecode) {
// TODO: Somehow evaluate 'bytecode'
}
public void unwrap(VMHandler handler) {
handler.handle(this);
}
}
List<∃B:VirtualMachine<B>> vms
hoặc for (∃B:VirtualMachine<B> vm : vms)
. (Vì đây là các loại chung, bạn không thể sử dụng các ?
ký tự đại diện của Java thay vì cú pháp "tự tạo"?) Tôi nghĩ rằng có thể có một ví dụ mã trong đó không có các loại chung chung như ∃B:VirtualMachine<B>
có liên quan, mà thay vào đó là "thẳng" ∃B
, bởi vì các loại chung dễ dàng được liên kết với các loại phổ quát sau các ví dụ mã đầu tiên của bạn.
∃B
rõ ràng về nơi định lượng đang xảy ra. Với cú pháp ký tự đại diện, bộ định lượng được ngụ ý ( List<List<?>>
thực tế có nghĩa là ∃T:List<List<T>>
không List<∃T:List<T>>
). Ngoài ra, định lượng rõ ràng cho phép bạn tham khảo loại (tôi đã sửa đổi ví dụ để tận dụng lợi thế này bằng cách lưu mã byte của loại B
trong một biến tạm thời).
Một giá trị của một kiểu tồn tại như ∃x. F(x)
là một cặp chứa một số loại x
và một giá trị của loại F(x)
. Trong khi đó một giá trị của một loại đa hình như ∀x. F(x)
là một hàm lấy một số loại x
và tạo ra một giá trị của loại F(x)
. Trong cả hai trường hợp, kiểu đóng trên một số hàm tạo F
.
Lưu ý rằng chế độ xem này trộn lẫn các loại và giá trị. Bằng chứng tồn tại là một loại và một giá trị. Bằng chứng phổ quát là toàn bộ họ các giá trị được lập chỉ mục theo loại (hoặc ánh xạ từ loại sang giá trị).
Vì vậy, sự khác biệt giữa hai loại bạn đã chỉ định như sau:
T = ∃X { X a; int f(X); }
Điều này có nghĩa là: Một giá trị của loại T
chứa một loại được gọi X
, một giá trị a:X
và một hàm f:X->int
. Nhà sản xuất các giá trị của loại T
được chọn bất kỳ loại nàoX
và người tiêu dùng không thể biết gì về nó X
. Ngoại trừ việc có một ví dụ về nó được gọi a
và giá trị này có thể được biến thành một int
bằng cách đưa nó vào f
. Nói cách khác, một giá trị của loại T
biết cách tạo ra một int
cách nào đó. Vâng, chúng tôi có thể loại bỏ các loại trung gian X
và chỉ cần nói:
T = int
Một số lượng phổ quát là một chút khác nhau.
T = ∀X { X a; int f(X); }
Điều này có nghĩa là: Một giá trị của loại T
có thể được cung cấp cho bất kỳ loại nào X
và nó sẽ tạo ra một giá trị a:X
và một hàm f:X->int
bất kể đó X
là gì . Nói cách khác: một người tiêu dùng các giá trị của loại T
có thể chọn bất kỳ loại nào cho X
. Và một nhà sản xuất các giá trị loại T
không thể biết bất cứ điều gì về X
nó, nhưng nó phải có khả năng tạo ra một giá trị a
cho bất kỳ lựa chọn nào X
và có thể biến giá trị đó thành một int
.
Rõ ràng việc thực hiện loại này là không thể, bởi vì không có chương trình nào có thể tạo ra giá trị của mọi loại có thể tưởng tượng được. Trừ khi bạn cho phép những điều vô lý như null
hoặc đáy.
Vì một tồn tại là một cặp, nên một đối số hiện sinh có thể được chuyển đổi thành một đối số phổ biến thông qua cà ri .
(∃b. F(b)) -> Int
giống như:
∀b. (F(b) -> Int)
Các cựu là một tồn tại hạng 2 . Điều này dẫn đến các thuộc tính hữu ích sau:
Mỗi loại thứ hạng
n+1
được định lượng tồn tại là một loại thứ hạng được định lượng toàn cầun
.
Có một thuật toán tiêu chuẩn để biến các tồn tại thành vũ trụ, được gọi là Skolemization .
Tôi nghĩ sẽ hợp lý khi giải thích các loại tồn tại cùng với các loại phổ quát, vì hai khái niệm này là bổ sung cho nhau, tức là một khái niệm "đối lập" với loại kia.
Tôi không thể trả lời từng chi tiết về các kiểu tồn tại (chẳng hạn như đưa ra một định nghĩa chính xác, liệt kê tất cả các cách sử dụng có thể, mối quan hệ của chúng với các loại dữ liệu trừu tượng, v.v.) bởi vì tôi đơn giản là không đủ hiểu biết về điều đó. Tôi sẽ chỉ trình bày (sử dụng Java) những gì bài viết HaskellWiki này nói là tác dụng chính của các loại tồn tại:
Các loại tồn tại có thể được sử dụng cho một số mục đích khác nhau. Nhưng những gì họ làm là 'ẩn' một biến loại ở phía bên tay phải. Thông thường, bất kỳ biến loại nào xuất hiện ở bên phải cũng phải xuất hiện ở bên trái [Rời]
Thiết lập ví dụ:
Mã giả sau đây không hoàn toàn hợp lệ với Java, mặc dù nó có thể đủ dễ để sửa nó. Trên thực tế, đó chính xác là những gì tôi sẽ làm trong câu trả lời này!
class Tree<α>
{
α value;
Tree<α> left;
Tree<α> right;
}
int height(Tree<α> t)
{
return (t != null) ? 1 + max( height(t.left), height(t.right) )
: 0;
}
Hãy để tôi đánh vần ngắn gọn điều này cho bạn. Chúng tôi đang xác định
một kiểu đệ quy Tree<α>
đại diện cho một nút trong cây nhị phân. Mỗi nút lưu trữ một value
số loại α và có các tham chiếu đến các tùy chọn left
và các right
cây con cùng loại.
một hàm height
trả về khoảng cách xa nhất từ bất kỳ nút lá nào đến nút gốc t
.
Bây giờ, hãy biến mã giả ở trên height
thành cú pháp Java thích hợp! (Tôi sẽ tiếp tục bỏ qua một số bản tóm tắt vì lý do ngắn gọn, chẳng hạn như định hướng đối tượng và sửa đổi khả năng truy cập.) Tôi sẽ trình bày hai giải pháp khả thi.
1. Giải pháp loại phổ quát:
Cách khắc phục rõ ràng nhất là chỉ đơn giản là tạo height
chung bằng cách đưa tham số loại α vào chữ ký của nó:
<α> int height(Tree<α> t)
{
return (t != null) ? 1 + max( height(t.left), height(t.right) )
: 0;
}
Điều này sẽ cho phép bạn khai báo các biến và tạo các biểu thức kiểu α bên trong hàm đó, nếu bạn muốn. Nhưng...
2. Giải pháp loại hiện sinh:
Nếu bạn nhìn vào cơ thể của phương thức của chúng tôi, bạn sẽ nhận thấy rằng chúng tôi không thực sự truy cập hoặc làm việc với bất kỳ thứ gì thuộc loại α ! Không có biểu thức nào có kiểu đó, cũng không có biến nào được khai báo với kiểu đó ... vậy, tại sao chúng ta phải tạo ra height
chung chung? Tại sao chúng ta không thể quên đi α ? Hóa ra, chúng ta có thể:
int height(Tree<?> t)
{
return (t != null) ? 1 + max( height(t.left), height(t.right) )
: 0;
}
Như tôi đã viết ở phần đầu của câu trả lời này, các kiểu tồn tại và phổ quát là bổ sung / kép về bản chất. Do đó, nếu giải pháp loại phổ quát là tạo ra chung chung height
hơn , thì chúng ta nên kỳ vọng rằng các loại tồn tại có tác dụng ngược lại: làm cho nó ít chung chung hơn , cụ thể bằng cách ẩn / xóa tham số loại α .
Kết quả là, bạn không còn có thể tham khảo loại t.value
trong phương thức này cũng như không thao tác bất kỳ biểu thức nào của loại đó, bởi vì không có định danh nào bị ràng buộc với nó. (Ký ?
tự đại diện là một mã thông báo đặc biệt, không phải là mã định danh "bắt giữ" một loại.) t.value
Đã thực sự trở nên mờ đục; có lẽ điều duy nhất bạn vẫn có thể làm với nó là loại nó Object
.
Tóm lược:
===========================================================
| universally existentially
| quantified type quantified type
---------------------+-------------------------------------
calling method |
needs to know | yes no
the type argument |
---------------------+-------------------------------------
called method |
can use / refer to | yes no
the type argument |
=====================+=====================================
Object
khá thú vị: Mặc dù cả hai đều giống nhau ở chỗ chúng cho phép bạn viết mã độc lập kiểu tĩnh, nhưng trước đây (generic) không bỏ đi gần như tất cả các thông tin loại có sẵn đạt được mục tiêu này Theo nghĩa đặc biệt này, thuốc generic là một phương thuốc cho Object
IMO.
public static void swap(List<?> list, int i, int j) { swapHelper(list, i, j); } private static <E> void swapHelper(List<E> list, int i, int j) { list.set(i, list.set(j, list.get(i))); }
, E
là một universal type
và ?
đại diện cho một existential type
?
?
trong int height(Tree<?> t)
vẫn chưa được biết bên trong hàm và vẫn được xác định bởi người gọi bởi vì đó là người gọi phải chọn cây nào để truyền vào. Ngay cả khi mọi người gọi đây là kiểu tồn tại trong Java, thì không. Trình ?
giữ chỗ có thể được sử dụng để triển khai một dạng tồn tại trong Java, trong một số trường hợp, nhưng đây không phải là một trong số chúng.
Đây là tất cả các ví dụ tốt, nhưng tôi chọn trả lời nó một chút khác nhau. Nhớ lại từ toán học, đó x. P (x) có nghĩa là "với mọi x, tôi có thể chứng minh rằng P (x)". Nói cách khác, nó là một loại hàm, bạn cho tôi một x và tôi có một phương pháp để chứng minh nó cho bạn.
Trong lý thuyết loại, chúng ta không nói về bằng chứng, mà là về loại. Vì vậy, trong không gian này, chúng tôi có nghĩa là "đối với bất kỳ loại X nào bạn cung cấp cho tôi, tôi sẽ cung cấp cho bạn một loại P cụ thể". Bây giờ, vì chúng tôi không cung cấp cho P nhiều thông tin về X bên cạnh thực tế rằng đó là một loại, P không thể làm gì nhiều với nó, nhưng có một số ví dụ. P có thể tạo loại "tất cả các cặp cùng loại" : P<X> = Pair<X, X> = (X, X)
. Hoặc chúng ta có thể tạo loại tùy chọn : P<X> = Option<X> = X | Nil
, trong đó Nil là loại con trỏ null. Chúng ta có thể tạo một danh sách từ nó : List<X> = (X, List<X>) | Nil
. Lưu ý rằng cái cuối cùng là đệ quy, các giá trị của List<X>
một trong hai cặp trong đó phần tử đầu tiên là X và phần tử thứ hai là mộtList<X>
hoặc nếu không nó là một con trỏ null.
Bây giờ, trong toán ∃x. P (x) có nghĩa là "Tôi có thể chứng minh rằng có một x cụ thể sao cho P (x) là đúng". Có thể có nhiều x như vậy, nhưng để chứng minh, một là đủ. Một cách khác để nghĩ về nó là phải tồn tại một tập hợp các cặp bằng chứng và bằng chứng không trống rỗng {(x, P (x))}.
Dịch theo lý thuyết loại: Một loại trong gia đình ∃X.P<X>
là loại X và loại tương ứng P<X>
. Lưu ý rằng trước khi chúng tôi đưa X cho P, (để chúng tôi biết mọi thứ về X nhưng P rất ít) thì điều ngược lại là đúng. P<X>
không hứa sẽ cung cấp bất kỳ thông tin nào về X, chỉ là có một thông tin và đó thực sự là một loại.
Điều này hữu ích như thế nào? Chà, P có thể là một loại có cách phơi bày loại bên trong X. Một ví dụ sẽ là một đối tượng che giấu biểu diễn bên trong của trạng thái X. Mặc dù chúng ta không có cách nào trực tiếp thao tác với nó, chúng ta có thể quan sát hiệu ứng của nó bằng cách chọc vào P. Có thể có nhiều triển khai kiểu này, nhưng bạn có thể sử dụng tất cả các loại này cho dù loại nào được chọn.
P<X>
thay vì P
(cùng chức năng và loại thùng chứa, giả sử, nhưng bạn không biết nó chứa X
)?
∀x. P(x)
không có nghĩa gì về khả năng chứng minh P(x)
, chỉ có sự thật.
Để trả lời trực tiếp câu hỏi của bạn:
Với loại phổ quát, việc sử dụng T
phải bao gồm tham số loại X
. Ví dụ T<String>
hay T<Integer>
. Đối với kiểu sử dụng T
hiện tại, không bao gồm tham số loại đó vì nó không xác định hoặc không liên quan - chỉ sử dụng T
(hoặc trong Java T<?>
).
Thêm thông tin:
Các loại phổ quát / trừu tượng và các loại tồn tại là một tính hai mặt của phối cảnh giữa người tiêu dùng / khách hàng của một đối tượng / chức năng và nhà sản xuất / thực hiện nó. Khi một bên nhìn thấy một loại phổ quát thì bên kia nhìn thấy một loại tồn tại.
Trong Java, bạn có thể định nghĩa một lớp chung:
public class MyClass<T> {
// T is existential in here
T whatever;
public MyClass(T w) { this.whatever = w; }
public static MyClass<?> secretMessage() { return new MyClass("bazzlebleeb"); }
}
// T is universal from out here
MyClass<String> mc1 = new MyClass("foo");
MyClass<Integer> mc2 = new MyClass(123);
MyClass<?> mc3 = MyClass.secretMessage();
MyClass
, T
là phổ quát vì bạn có thể thay thế bất kỳ loại choT
khi bạn sử dụng lớp đó và bạn phải biết loại thực tế của T bất cứ khi nào bạn sử dụng một thể hiện củaMyClass
MyClass
,T
tồn tại bởi vì nó không biết loại thực sự củaT
?
đại diện cho kiểu tồn tại - do đó, khi bạn ở trong lớp, T
về cơ bản ?
. Nếu bạn muốn xử lý một thể hiện MyClass
với tính T
tồn tại, bạn có thể khai báo MyClass<?>
như trong secretMessage()
ví dụ trên.Các kiểu hiện sinh đôi khi được sử dụng để ẩn các chi tiết thực hiện của một cái gì đó, như được thảo luận ở nơi khác. Một phiên bản Java của cái này có thể trông giống như:
public class ToDraw<T> {
T obj;
Function<Pair<T,Graphics>, Void> draw;
ToDraw(T obj, Function<Pair<T,Graphics>, Void>
static void draw(ToDraw<?> d, Graphics g) { d.draw.apply(new Pair(d.obj, g)); }
}
// Now you can put these in a list and draw them like so:
List<ToDraw<?>> drawList = ... ;
for(td in drawList) ToDraw.draw(td);
Có một chút khó khăn để nắm bắt điều này một cách chính xác bởi vì tôi đang giả vờ ở một loại ngôn ngữ lập trình chức năng nào đó, mà Java thì không. Nhưng vấn đề ở đây là bạn đang nắm bắt một số loại trạng thái cộng với một danh sách các hàm hoạt động ở trạng thái đó và bạn không biết loại thực sự của phần trạng thái, nhưng các hàm đã làm vì chúng đã khớp với loại đó rồi .
Bây giờ, trong Java tất cả các loại không nguyên thủy không phải là nguyên thủy đều tồn tại một phần. Điều này nghe có vẻ lạ, nhưng vì một biến được khai báo Object
có khả năng là một lớp con Object
thay vào đó, bạn không thể khai báo loại cụ thể, chỉ "loại này hoặc một lớp con". Và do đó, các đối tượng được biểu diễn dưới dạng một chút trạng thái cộng với một danh sách các hàm hoạt động trên trạng thái đó - chính xác là hàm nào được gọi được xác định khi chạy bằng cách tra cứu. Điều này rất giống với việc sử dụng các loại tồn tại ở trên nơi bạn có một phần trạng thái tồn tại và một chức năng hoạt động trên trạng thái đó.
Trong các ngôn ngữ lập trình được gõ tĩnh mà không có phân nhóm và phôi, các kiểu tồn tại cho phép một người quản lý danh sách các đối tượng được gõ khác nhau. Một danh sách T<Int>
không thể chứa a T<Long>
. Tuy nhiên, một danh sách T<?>
có thể chứa bất kỳ biến thể nào T
, cho phép một người đưa nhiều loại dữ liệu khác nhau vào danh sách và chuyển đổi tất cả thành int (hoặc thực hiện bất kỳ thao tác nào được cung cấp bên trong cấu trúc dữ liệu) theo yêu cầu.
Người ta có thể luôn luôn chuyển đổi một bản ghi với một loại tồn tại thành một bản ghi mà không cần sử dụng các bao đóng. Một đóng cũng tồn tại được gõ, trong đó các biến miễn phí mà nó được đóng lại được ẩn khỏi người gọi. Do đó, một ngôn ngữ hỗ trợ các kiểu đóng nhưng không phải kiểu tồn tại có thể cho phép bạn tạo các bao đóng có cùng trạng thái ẩn mà bạn sẽ đặt vào phần tồn tại của một đối tượng.
Một loại tồn tại là một loại mờ.
Hãy nghĩ về một tệp xử lý trong Unix. Bạn biết loại của nó là int, vì vậy bạn có thể dễ dàng giả mạo nó. Ví dụ, bạn có thể thử đọc từ tay cầm 43. Nếu điều đó xảy ra là chương trình có một tệp được mở bằng tay cầm cụ thể này, bạn sẽ đọc từ nó. Mã của bạn không phải là độc hại, chỉ là cẩu thả (ví dụ, tay cầm có thể là một biến chưa được khởi tạo).
Một kiểu tồn tại được ẩn khỏi chương trình của bạn. Nếu fopen
trả về một kiểu tồn tại, tất cả những gì bạn có thể làm với nó là sử dụng nó với một số hàm thư viện chấp nhận kiểu tồn tại này. Ví dụ, mã giả sau đây sẽ biên dịch:
let exfile = fopen("foo.txt"); // No type for exfile!
read(exfile, buf, size);
Giao diện "đọc" được khai báo là:
Tồn tại một loại T sao cho:
size_t read(T exfile, char* buf, size_t size);
Biến exfile không phải là int, không phải a char*
, không phải là tệp File struct, không có gì bạn có thể diễn tả trong hệ thống kiểu. Bạn không thể khai báo một biến có loại không xác định và bạn không thể sử dụng một con trỏ vào loại không xác định đó. Ngôn ngữ sẽ không cho phép bạn.
read
là ∃T.read(T file, ...)
sau đó không có gì là bạn có thể vượt qua trong tham số đầu tiên. Điều gì sẽ làm việc là fopen
trả về tay cầm tập tin và chức năng đọc được phân chia theo cùng một tồn tại :∃T.(T, read(T file, ...))
Có vẻ như tôi đến hơi muộn, nhưng dù sao, tài liệu này bổ sung một cái nhìn khác về các loại tồn tại, mặc dù không cụ thể là ngôn ngữ, nhưng nó sẽ dễ hiểu hơn về các loại tồn tại: http: //www.cs.uu .nl / nhóm / ST / Dự án / ehc / ehc-book.pdf (chương 8)
Sự khác biệt giữa một loại định lượng phổ biến và tồn tại có thể được đặc trưng bởi quan sát sau đây:
Việc sử dụng một giá trị với loại được định lượng determines xác định loại để chọn cho việc khởi tạo biến loại được định lượng. Ví dụ: người gọi hàm nhận dạng, id id :: ∀aa → a, xác định loại cần chọn cho biến loại a cho ứng dụng id cụ thể này. Đối với ứng dụng chức năng, id id 3, loại này bằng Int.
Việc tạo ra một giá trị với kiểu ∃ định lượng xác định và ẩn, kiểu của biến loại được định lượng. Ví dụ: một người tạo ra một “a. (A, a → Int) có thể đã xây dựng một giá trị của loại đó từ Hồi (3, λx → x) một người tạo khác đã xây dựng một giá trị với cùng loại từ ((x x, λx → ord x). Từ quan điểm người dùng, cả hai giá trị có cùng loại và do đó có thể hoán đổi cho nhau. Giá trị có một loại cụ thể được chọn cho biến loại a, nhưng chúng tôi không biết loại nào, vì vậy thông tin này không còn có thể được khai thác. Thông tin loại giá trị cụ thể này đã bị 'lãng quên'; chúng tôi chỉ biết nó tồn tại.
Một loại phổ biến tồn tại cho tất cả các giá trị của (các) tham số loại. Một kiểu tồn tại chỉ tồn tại cho các giá trị của (các) tham số loại thỏa mãn các ràng buộc của kiểu tồn tại.
Ví dụ, trong Scala, một cách để thể hiện một kiểu tồn tại là một kiểu trừu tượng bị ràng buộc với một số giới hạn trên hoặc dưới.
trait Existential {
type Parameter <: Interface
}
Tương đương một loại phổ quát bị ràng buộc là một loại tồn tại như trong ví dụ sau.
trait Existential[Parameter <: Interface]
Bất kỳ trang web sử dụng nào cũng có thể sử dụng Interface
bởi vì bất kỳ kiểu con khả thi nào Existential
phải xác định type Parameter
cái phải thực hiện Interface
.
Một trường hợp suy biến của một kiểu tồn tại trong Scala là một kiểu trừu tượng không bao giờ được đề cập và do đó không cần phải được định nghĩa bởi bất kỳ kiểu con nào. Điều này thực sự có một ký hiệu viết tắt List[_]
trong Scala và List<?>
trong Java.
Câu trả lời của tôi được lấy cảm hứng từ đề xuất của Martin Oderky để thống nhất các loại trừu tượng và hiện sinh. Các slide hỗ trợ hiểu biết.
∀x.f(x)
, mờ đục đối với các chức năng nhận của chúng trong khi Các loại Hiện sinh ∃x.f(x)
, bị hạn chế để có các thuộc tính nhất định. Thông thường, tất cả các tham số là Existential vì chức năng của chúng sẽ thao tác trực tiếp với chúng; tuy nhiên, các tham số chung có thể có các loại là Universal vì hàm sẽ không quản lý chúng ngoài các hoạt động phổ quát cơ bản như lấy tham chiếu như trong:∀x.∃array.copy(src:array[x] dst:array[x]){...}
forSome
định lượng tồn tại tham số loại.
Nghiên cứu về các kiểu dữ liệu trừu tượng và ẩn thông tin đã đưa các kiểu tồn tại vào các ngôn ngữ lập trình. Tạo một kiểu tóm tắt kiểu dữ liệu ẩn thông tin về kiểu đó, vì vậy một máy khách kiểu đó không thể lạm dụng nó. Giả sử bạn đã có một tham chiếu đến một đối tượng ... một số ngôn ngữ cho phép bạn chuyển tham chiếu đó đến một tham chiếu đến byte và làm bất cứ điều gì bạn muốn với phần bộ nhớ đó. Với mục đích đảm bảo hành vi của một chương trình, ngôn ngữ sẽ hữu ích khi bạn thực thi rằng bạn chỉ hành động dựa trên tham chiếu đến đối tượng thông qua các phương thức mà người thiết kế đối tượng cung cấp. Bạn biết loại tồn tại, nhưng không có gì hơn.
Xem:
Các loại trừu tượng có Loại Hiện tại, MITCHEL & PLOTKIN
Theo tôi hiểu đó là một cách toán học để mô tả các giao diện / lớp trừu tượng.
Còn đối với T = ∃X {X a; int f (X); }
Đối với C #, nó sẽ dịch sang một loại trừu tượng chung:
abstract class MyType<T>{
private T a;
public abstract int f(T x);
}
"Hiện sinh" chỉ có nghĩa là có một số loại tuân theo các quy tắc được xác định ở đây.