Có một lý do để có một loại dưới cùng trong một ngôn ngữ lập trình?


49

Một loại đáy là một cấu trúc chủ yếu xuất hiện trong lý thuyết loại toán học. Nó cũng được gọi là loại trống. Nó là một loại không có giá trị, nhưng là một kiểu con của tất cả các loại.

Nếu kiểu trả về của hàm là loại dưới cùng, điều đó có nghĩa là nó không trả về. Giai đoạn = Stage. Có thể nó lặp đi lặp lại mãi mãi, hoặc có thể nó ném một ngoại lệ.

Điểm có loại kỳ lạ này trong ngôn ngữ lập trình là gì? Nó không phổ biến, nhưng nó có mặt ở một số, như Scala và Lisp.


2
@SargeBorsch: bạn có chắc chắn về điều đó? Tất nhiên người ta không thể xác định rõ ràng một voiddữ liệu trong C ...
Basile Starynkevitch

3
@BasileStarynkevitch không có giá trị của loại voidvà loại đơn vị phải có một giá trị. Ngoài ra, như bạn đã chỉ ra, bạn thậm chí không thể khai báo một giá trị của loại void, điều đó có nghĩa là nó thậm chí không phải là một loại, chỉ là một trường hợp góc đặc biệt trong ngôn ngữ.
Sange Borsch

2
Vâng, C là kỳ lạ trong điều này, đặc biệt là trong cách viết con trỏ và các loại con trỏ hàm. Nhưng voidtrong Java thì gần giống nhau: không thực sự là một loại & không thể có các giá trị.
Sange Borsch

3
Trong ngữ nghĩa của các ngôn ngữ có loại dưới cùng, loại dưới cùng không được coi là không có giá trị, mà là có một giá trị, giá trị dưới cùng, đại diện cho một tính toán không bao giờ hoàn thành (thông thường). Vì giá trị dưới cùng là giá trị của mọi loại, nên loại dưới cùng có thể là một kiểu con của mọi loại.
Theodore Norvell

4
@BasileStarynkevitch Lisp thông thường có loại nil không có giá trị. Nó cũng có loại null chỉ có một giá trị, ký hiệu nil(aka, ()), là loại đơn vị.
Joshua Taylor

Câu trả lời:


33

Tôi sẽ lấy một ví dụ đơn giản: C ++ vs Rust.

Đây là một hàm được sử dụng để ném ngoại lệ trong C ++ 11:

[[noreturn]] void ThrowException(char const* message,
                                 char const* file,
                                 int line,
                                 char const* function);

Và đây là tương đương trong Rust:

fn formatted_panic(message: &str, file: &str, line: isize, function: &str) -> !;

Trên một vấn đề hoàn toàn cú pháp, cấu trúc Rust là hợp lý hơn. Lưu ý rằng cấu trúc C ++ chỉ định loại trả về mặc dù nó cũng chỉ định nó sẽ không trả về. Điều đó hơi lạ.

Trên một ghi chú tiêu chuẩn, cú pháp C ++ chỉ xuất hiện với C ++ 11 (nó được giải quyết trên đầu trang), nhưng các trình biên dịch khác nhau đã cung cấp các phần mở rộng khác nhau trong một thời gian, do đó các công cụ phân tích của bên thứ ba phải được lập trình để nhận ra các cách khác nhau thuộc tính này có thể được viết. Có nó tiêu chuẩn hóa rõ ràng là vượt trội.


Bây giờ, vì lợi ích?

Thực tế là một hàm không trả về có thể hữu ích cho:

  • tối ưu hóa: người ta có thể cắt bất kỳ mã nào sau nó (nó sẽ không trở lại), không cần phải lưu các thanh ghi (vì không cần thiết phải khôi phục chúng), ...
  • phân tích tĩnh: nó loại bỏ một số đường dẫn thực thi tiềm năng
  • khả năng bảo trì: (xem phân tích tĩnh, nhưng bởi con người)

6
voidtrong ví dụ C ++ của bạn định nghĩa (một phần) kiểu của hàm - không phải kiểu trả về. Nó không giới hạn giá trị mà hàm được phép return; bất cứ điều gì có thể chuyển đổi thành void (không có gì). Nếu hàm returns không được theo sau bởi một giá trị. Các loại đầy đủ của chức năng là void () (char const*, char const*, int, char const *). + 1 để sử dụng char constthay vì const char:-)
Xóa

4
Điều đó không có nghĩa là có ý nghĩa hơn khi có một loại dưới cùng, chỉ là nó có ý nghĩa để chú thích các chức năng về việc chúng có trở lại hay không là một phần của ngôn ngữ. Trên thực tế, vì các hàm có thể không quay trở lại do các lý do khác nhau, nên có vẻ tốt hơn để mã hóa lý do theo một cách nào đó thay vì sử dụng thuật ngữ bắt tất cả, giống như khái niệm tương đối gần đây về các hàm chú thích dựa trên các tác dụng phụ của chúng.
GregRos

2
Trên thực tế, có một lý do để làm cho "không trả về" và "có loại trả về X" độc lập: Khả năng tương thích ngược cho mã của riêng bạn, vì quy ước gọi có thể phụ thuộc vào loại trả về.
Ded repeatator

[[noreturn]] mệnh của cú pháp hoặc bổ sung chức năng?
Zaibis

1
[cont.] Nhìn chung, tôi chỉ nói rằng một cuộc thảo luận về những lợi thế của phải xác định những gì đủ điều kiện là một triển khai của; và tôi không nghĩ rằng một hệ thống loại không có ( a → ⊥) ( ab ) là một triển khai hữu ích của. Vì vậy, theo nghĩa này, SysV x86-64 C ABI (trong số những người khác) không cho phép thực hiện.
Alex Shpilkin

26

Câu trả lời của Karl là tốt. Đây là một cách sử dụng bổ sung mà tôi không nghĩ có ai khác đã đề cập. Các loại

if E then A else B

phải là một loại bao gồm tất cả các giá trị trong loại Avà tất cả các giá trị trong loại B. Nếu loại BNothing, thì loại ifbiểu thức có thể là loại A. Tôi sẽ thường xuyên tuyên bố một thói quen

def unreachable( s:String ) : Nothing = throw new AssertionError("Unreachable "+s) 

để nói rằng mã dự kiến ​​sẽ không đạt được. Vì loại của nó là Nothing, unreachable(s)bây giờ có thể được sử dụng trong bất kỳ ifhoặc (thường xuyên hơn) switchmà không ảnh hưởng đến loại kết quả. Ví dụ

 val colour : Colour := switch state of
         BLACK_TO_MOVE: BLACK
         WHITE_TO_MOVE: WHITE
         default: unreachable("Bad state")

Scala có một loại Không có gì.

Một trường hợp sử dụng khác cho Nothing(như được đề cập trong câu trả lời của Karl) là Danh sách [Không có gì] là loại danh sách mà mỗi thành viên có loại Không có gì. Do đó, nó có thể là loại danh sách trống.

Thuộc tính quan trọng của Nothingcác trường hợp sử dụng này hoạt động không phải là nó không có giá trị - mặc dù trong Scala, chẳng hạn, nó không có giá trị - đó là kiểu con của mọi loại khác.

Giả sử bạn có một ngôn ngữ trong đó mọi loại đều chứa cùng một giá trị - hãy gọi nó (). Trong ngôn ngữ như vậy, loại đơn vị, có ()giá trị duy nhất, có thể là một kiểu con của mọi loại. Điều đó không làm cho nó trở thành một loại dưới cùng theo nghĩa của OP; OP rõ ràng là loại dưới cùng không chứa giá trị. Tuy nhiên, vì nó là một kiểu con của mọi loại, nó có thể đóng vai trò tương tự như một loại dưới cùng.

Haskell làm mọi thứ một chút khác nhau. Trong Haskell, một biểu thức không bao giờ tạo ra giá trị có thể có sơ đồ kiểu forall a.a. Một thể hiện của sơ đồ loại này sẽ hợp nhất với bất kỳ loại nào khác, do đó, nó hoạt động hiệu quả như một loại dưới cùng, mặc dù (tiêu chuẩn) Haskell không có khái niệm về phân nhóm. Ví dụ, errorhàm từ khúc dạo đầu tiêu chuẩn có sơ đồ kiểu forall a. [Char] -> a. Vì vậy bạn có thể viết

if E then A else error ""

và loại biểu thức sẽ giống như loại A, cho bất kỳ biểu thức nào A.

Danh sách trống trong Haskell có sơ đồ loại forall a. [a]. Nếu Alà một biểu thức có kiểu là loại danh sách, thì

if E then A else []

là một biểu thức có cùng loại với A.


Sự khác biệt giữa loại forall a . [a]và loại [a]trong Haskell là gì? Các biến loại không được định lượng phổ biến trong các biểu thức loại Haskell?
Giorgio

@Giorgio Trong Haskell, định lượng phổ quát là ẩn nếu rõ ràng rằng bạn đang xem sơ đồ kiểu. Bạn thậm chí không thể viết forallbằng Haskell tiêu chuẩn năm 2010. Tôi đã viết định lượng một cách rõ ràng bởi vì đây không phải là một diễn đàn Haskell và một số người có thể không quen thuộc với các quy ước của Haskell. Vì vậy, không có sự khác biệt ngoại trừ đó forall a . [a]là không chuẩn trong khi đó [a]là.
Theodore Norvell

19

Các loại tạo thành một monoid theo hai cách, cùng nhau tạo ra một nửa . Đó là những gì được gọi là các loại dữ liệu đại số . Đối với các loại hữu hạn, việc tạo nửa này liên quan trực tiếp đến việc tạo các số tự nhiên (bao gồm 0), có nghĩa là bạn đếm có bao nhiêu giá trị có thể có của loại này (không bao gồm các giá trị không biến đổi).

  • Các loại đáy (Tôi sẽ gọi nó Vacuous) có giá trị zero .
  • Loại đơn vị có một giá trị. Tôi sẽ gọi cả loại và giá trị đơn của nó ().
  • Thành phần (mà hầu hết các ngôn ngữ lập trình hỗ trợ khá trực tiếp, thông qua các bản ghi / cấu trúc / lớp với các trường công khai) là một hoạt động sản phẩm . Ví dụ, (Bool, Bool)có bốn giá trị có thể, cụ thể là (False,False), (False,True), (True,False)(True,True).
    Loại đơn vị là thành phần nhận dạng của hoạt động sáng tác. Ví dụ ((), False)((), True)là các giá trị duy nhất của loại ((), Bool), vì vậy loại này là đẳng cấu với Boolchính nó.
  • Các loại thay thế có phần bị bỏ qua trong hầu hết các ngôn ngữ (loại ngôn ngữ OO hỗ trợ chúng với tính kế thừa), nhưng chúng không kém phần hữu ích. Một thay thế giữa hai loại ABvề cơ bản có tất cả các giá trị A, cộng với tất cả các giá trị của B, do đó loại tổng . Ví dụ, Either () Boolcó ba giá trị, tôi sẽ gọi cho họ Left (), Right FalseRight True.
    Các loại chốt là các yếu tố bản sắc của tổng: Either Vacuous Ađã chỉ đánh giá cao của các hình thức Right a, bởi vì Left ...không có ý nghĩa ( Vacuouskhông có giá trị).

Điều thú vị về các đơn âm này là, khi bạn giới thiệu các chức năng cho ngôn ngữ của mình, danh mục của các loại này với các chức năng như hình thái là một thể loại đơn hình . Trong số những thứ khác, điều này cho phép bạn xác định functors applicative và monads , mà bật ra được một sự trừu tượng tuyệt vời cho phép tính tổng quát (có thể liên quan đến tác dụng phụ, vv) trong thuật ngữ khác hoàn toàn chức năng.

Bây giờ, thực sự bạn có thể đi khá xa với việc chỉ lo lắng một mặt của vấn đề (đơn điệu về bố cục), sau đó bạn không thực sự cần loại dưới cùng một cách rõ ràng. Chẳng hạn, ngay cả Haskell đã làm trong một thời gian dài không có loại đáy tiêu chuẩn. Bây giờ nó có, nó được gọi là Void.

Nhưng khi bạn xem xét bức tranh đầy đủ, như một thể loại khép kín của bicartesian , thì hệ thống loại thực sự tương đương với toàn bộ tính toán lambda, vì vậy về cơ bản, bạn có sự trừu tượng hoàn hảo đối với mọi thứ có thể trong ngôn ngữ Turing-perfect. Tuyệt vời cho các ngôn ngữ dành riêng cho miền được nhúng, ví dụ, có một dự án về mã hóa trực tiếp các mạch điện tử theo cách này .

Tất nhiên, bạn cũng có thể nói rằng đây là tất cả những điều vô nghĩa chung của các nhà lý thuyết . Bạn hoàn toàn không cần biết về lý thuyết thể loại để trở thành một lập trình viên giỏi, nhưng khi bạn làm điều đó, nó mang đến cho bạn những cách tổng quát mạnh mẽ và lố bịch để lý luận về mã, và đưa ra những bất biến.


mb21 nhắc nhở tôi lưu ý rằng điều này không nên bị nhầm lẫn với các giá trị dưới cùng . Trong các ngôn ngữ lười biếng như Haskell, mọi loại đều chứa giá trị dưới cùng, được ký hiệu . Đây không phải là một điều cụ thể mà bạn có thể vượt qua một cách rõ ràng, thay vào đó là những gì mà Wikipedia đã trả lại, ví dụ như khi một chức năng lặp đi lặp lại mãi mãi. Ngay cả Voidloại kiểu Haskell cũng có chứa giá trị dưới cùng, do đó là tên. Trong ánh sáng đó, loại dưới cùng của Haskell thực sự có một giá trị và loại đơn vị của nó có hai giá trị, nhưng trong thảo luận về lý thuyết loại này thường bị bỏ qua.


"Loại dưới cùng (tôi sẽ gọi nó Void)", không bị nhầm lẫn với giá trị bottom , là thành viên của bất kỳ loại nào trong Haskell .
mb21

18

Có thể nó lặp đi lặp lại mãi mãi, hoặc có thể nó ném một ngoại lệ.

Âm thanh giống như một loại hữu ích để có trong những tình huống đó, hiếm khi chúng có thể.

Ngoài ra, mặc dù Nothing(tên của Scala cho loại dưới cùng) có thể không có giá trị, List[Nothing]không có hạn chế đó, điều này làm cho nó hữu ích như loại danh sách trống. Hầu hết các ngôn ngữ đều khắc phục điều này bằng cách tạo một danh sách các chuỗi trống thành một loại khác với một danh sách các số nguyên trống, có ý nghĩa, nhưng làm cho một danh sách trống dài hơn để viết, đó là một nhược điểm lớn trong ngôn ngữ hướng danh sách.


12
Danh sách trống của hoàng Haskell là một nhà xây dựng kiểu: chắc chắn điều liên quan ở đây là nó đa hình hoặc quá tải - nghĩa là các danh sách trống từ các loại khác nhau là các giá trị riêng biệt, nhưng []đại diện cho tất cả chúng, và sẽ được xác định theo các loại cụ thể khi cần thiết.
Peter LeFanu Lumsdaine

Thật thú vị: Nếu bạn cố gắng tạo một mảng trống trong trình thông dịch Haskell, bạn sẽ nhận được một giá trị rất xác định với một loại rất không xác định : [a]. Tương tự, :t Left 1sản lượng Num a => Either a b. Trên thực tế việc đánh giá biểu thức buộc loại a, nhưng không phải b:Either Integer b
John Dvorak

5
Danh sách trống là một hàm tạo giá trị . Một chút khó hiểu, hàm tạo kiểu có liên quan có cùng tên nhưng bản thân danh sách trống là một giá trị không phải là một loại (tốt, có cả danh sách cấp độ loại, nhưng đó là một chủ đề hoàn toàn khác). Phần làm cho danh sách trống hoạt động cho bất kỳ loại danh sách nào là hàm ý foralltrong loại của nó , forall a. [a]. Có một số cách tốt để suy nghĩ forall, nhưng phải mất một thời gian để thực sự tìm ra.
David

@PeterLeFanuLumsdaine Đó chính xác là ý nghĩa của việc xây dựng kiểu. Nó chỉ có nghĩa là nó là một loại với một loại khác nhau *.
GregRos

2
Trong Haskell []là một hàm tạo kiểu và []là một biểu thức biểu thị một danh sách trống. Nhưng điều đó không có nghĩa là "danh sách trống của Haskell là một hàm tạo kiểu". Bối cảnh cho thấy rõ liệu []đang được sử dụng như một loại hoặc như một biểu thức. Giả sử bạn khai báo data Foo x = Foo | Bar x (Foo x); bây giờ bạn có thể sử dụng Foonhư một hàm tạo kiểu hoặc như một giá trị, nhưng thật tình cờ là bạn đã chọn cùng một tên cho cả hai.
Theodore Norvell

3

Nó rất hữu ích cho phân tích tĩnh để ghi lại thực tế rằng một đường dẫn mã cụ thể không thể truy cập được. Ví dụ: nếu bạn viết như sau trong C #:

int F(int arg) {
 if (arg != 0)
  return arg + 1; //some computation
 else
  Assert(false); //this throws but the compiler does not know that
}
void Assert(bool cond) { if (!cond) throw ...; }

Trình biên dịch sẽ phàn nàn rằng Fkhông trả về bất cứ thứ gì trong ít nhất một đường dẫn mã. Nếu Assertđược đánh dấu là không trả về, trình biên dịch sẽ không cần phải cảnh báo.


2

Trong một số ngôn ngữ, nullcó loại dưới cùng, vì kiểu con của tất cả các loại xác định chính xác ngôn ngữ sử dụng null cho cái gì (mặc dù mâu thuẫn nhẹ là có nullcả chính nó và một hàm trả về chính nó, tránh các đối số phổ biến về lý do tại sao botkhông nên có người ở).

Nó cũng có thể được sử dụng như một hàm bắt tất cả trong các loại hàm ( any -> bot) để xử lý công văn bị sai lệch.

Và một số ngôn ngữ cho phép bạn thực sự giải quyết botnhư một lỗi, có thể được sử dụng để cung cấp các lỗi biên dịch tùy chỉnh.


11
Không, một loại dưới cùng không phải là loại đơn vị. Loại dưới cùng hoàn toàn không có giá trị, do đó, hàm trả về loại dưới không nên trả về (nghĩa là ném ngoại lệ hoặc vòng lặp vô thời hạn)
Basile Starynkevitch

@BasileStarynkevitch - Tôi không nói về loại đơn vị. Loại đơn vị ánh xạ sang voidcác ngôn ngữ phổ biến (mặc dù có ngữ nghĩa hơi khác nhau cho cùng một mục đích sử dụng), không null. Mặc dù bạn cũng đúng rằng hầu hết các ngôn ngữ không mô hình null là loại dưới cùng.
Telastyn

3
@TheodoreNorvell - phiên bản đầu tiên của Tangent đã làm điều đó - mặc dù tôi là tác giả, nên có lẽ đó là gian lận. Tôi không có các liên kết được lưu cho người khác và đã được một thời gian kể từ khi tôi thực hiện nghiên cứu đó.
Telastyn

1
@Martijn Nhưng bạn có thể sử dụng null, ví dụ: bạn so sánh một con trỏ với nullkết quả Boolean. Tôi nghĩ rằng các câu trả lời đang chỉ ra rằng có hai loại đáy khác nhau. (a) Ngôn ngữ (ví dụ Scala) trong đó loại là kiểu con của mọi loại đại diện cho các tính toán không mang lại bất kỳ kết quả nào. Về cơ bản, đây là một loại trống, mặc dù về mặt kỹ thuật thường được tạo ra bởi một giá trị dưới cùng vô dụng đại diện cho sự hủy diệt. (b) Các ngôn ngữ như Tangent, trong đó loại dưới cùng là tập hợp con của mọi loại khác vì nó chứa một giá trị hữu ích cũng được tìm thấy trong mọi loại khác - null.
Theodore Norvell

4
Thật thú vị khi một số ngôn ngữ có giá trị với loại bạn không thể khai báo (phổ biến cho chữ null) và ngôn ngữ khác có loại bạn có thể khai báo nhưng không có giá trị (loại dưới cùng truyền thống) và chúng có vai trò tương đối .
Martijn

1

Vâng, đây là một loại khá hữu ích; trong khi vai trò của nó chủ yếu là nội thất đối với hệ thống loại, có một số trường hợp loại dưới cùng sẽ xuất hiện một cách công khai.

Hãy xem xét một ngôn ngữ được gõ tĩnh trong đó các điều kiện là các biểu thức (do đó, cấu trúc if-then-other nhân đôi toán tử ternary của C và bạn bè, và có thể có một tuyên bố trường hợp đa chiều tương tự). Ngôn ngữ lập trình chức năng có điều này, nhưng nó cũng xảy ra trong một số ngôn ngữ bắt buộc nhất định (kể từ ALGOL 60). Sau đó, tất cả các biểu thức nhánh cuối cùng phải tạo ra loại của toàn bộ biểu thức điều kiện. Người ta có thể đơn giản yêu cầu các loại của chúng bằng nhau (và tôi nghĩ đây là trường hợp của toán tử ternary trong C) nhưng điều này là quá hạn chế đặc biệt là khi điều kiện cũng có thể được sử dụng như câu lệnh có điều kiện (không trả về bất kỳ giá trị hữu ích nào). Nói chung, người ta muốn mỗi biểu thức nhánh được chuyển đổi (ngầm) đến một loại phổ biến sẽ là loại biểu thức đầy đủ (có thể có nhiều hạn chế phức tạp hơn hoặc ít hơn để cho phép loại phổ biến đó được trình biên dịch tìm thấy một cách hiệu quả, xem C ++, nhưng tôi sẽ không đi sâu vào các chi tiết đó ở đây).

Có hai loại tình huống trong đó một loại chuyển đổi chung sẽ cho phép sự linh hoạt cần thiết của các biểu thức điều kiện đó. Một đã được đề cập, trong đó loại kết quả là loại đơn vịvoid; đây tự nhiên là một siêu kiểu của tất cả các loại khác và cho phép bất kỳ loại nào được chuyển đổi (tầm thường) thành nó làm cho nó có thể sử dụng biểu thức điều kiện làm câu lệnh điều kiện. Cái khác liên quan đến các trường hợp biểu thức không trả về giá trị hữu ích, nhưng một hoặc nhiều nhánh không có khả năng tạo ra một giá trị. Họ thường sẽ đưa ra một ngoại lệ hoặc liên quan đến một bước nhảy và yêu cầu họ (cũng) tạo ra một giá trị của loại biểu thức (từ một điểm không thể truy cập) sẽ là vô nghĩa. Chính loại tình huống này có thể được xử lý một cách duyên dáng bằng cách đưa ra các mệnh đề, bước nhảy và các lệnh gọi ngoại lệ sẽ có tác động như vậy, loại dưới cùng, một loại có thể (tầm thường) được chuyển đổi thành bất kỳ loại nào khác.

Tôi sẽ đề nghị viết một loại dưới cùng như vậy *để đề xuất khả năng chuyển đổi của nó thành loại tùy ý. Ví dụ, nó có thể phục vụ các mục đích hữu ích khác khi cố gắng suy ra loại kết quả cho hàm đệ quy không khai báo bất kỳ, loại suy ra có thể gán loại *cho bất kỳ lệnh gọi đệ quy nào để tránh tình huống gà và trứng; loại thực tế sẽ được xác định bởi các nhánh không đệ quy và các nhánh đệ quy sẽ được chuyển đổi thành loại phổ biến của các nhánh không đệ quy. Nếu hoàn toàn không có các nhánh không đệ quy, kiểu sẽ vẫn còn *và chỉ ra chính xác rằng hàm không có cách nào có thể trở lại từ đệ quy. Khác với loại chức năng ném ngoại lệ này, người ta có thể sử dụng*như loại thành phần của chuỗi có độ dài 0, ví dụ như danh sách trống; một lần nữa nếu bao giờ một phần tử được chọn từ một biểu thức loại [*](danh sách trống nhất thiết), thì loại kết quả *sẽ chỉ ra chính xác rằng điều này không bao giờ có thể quay lại mà không có lỗi.


Vì vậy, ý tưởng var foo = someCondition() ? functionReturningBar() : functionThatAlwaysThrows()có thể suy ra loại foonhư Bar, vì biểu thức không bao giờ có thể mang lại bất cứ điều gì khác?
supercat

1
Bạn vừa mô tả loại đơn vị ít nhất trong phần đầu câu trả lời của bạn. Một chức năng mà trả về kiểu đơn vị là giống như một trong đó được khai báo là trở về voidtrong C. Phần thứ hai của câu trả lời của bạn, nơi bạn nói về một kiểu cho một chức năng mà không bao giờ trở lại, hoặc một danh sách không có elements- đó thực sự loại dưới cùng! (Nó thường được viết _|_thay vì *. Không chắc tại sao. Có lẽ vì nó trông giống đáy (con người) :)
andrewf

2
Để tránh nghi ngờ: 'không trả lại bất cứ điều gì hữu ích' khác với 'không trả lại'; đầu tiên được đại diện bởi loại Đơn vị; thứ hai theo loại Dưới cùng.
andrewf

@andrewf: Có tôi hiểu sự khác biệt. Câu trả lời của tôi hơi dài dòng, nhưng điểm tôi muốn đưa ra là loại đơn vị và loại dưới cùng đóng vai trò tương đương (khác nhau nhưng) trong việc cho phép một số biểu thức nhất định được sử dụng linh hoạt hơn (nhưng vẫn an toàn).
Marc van Leeuwen

@supercat: Vâng, đó là ý tưởng. Hiện nay trong C ++ đó là bất hợp pháp, mặc dù nó sẽ có giá trị nếu functionThatAlwaysThrows()được thay thế bằng một rõ ràng throw, do ngôn ngữ đặc biệt trong tiêu chuẩn. Có một loại mà điều này sẽ là một cải tiến.
Marc van Leeuwen

0

Trong một số ngôn ngữ, bạn có thể chú thích một hàm để thông báo cho cả trình biên dịch và nhà phát triển rằng một cuộc gọi đến hàm này sẽ không trả về (và nếu hàm được viết theo cách có thể trả về, trình biên dịch sẽ không cho phép ). Đó là một điều hữu ích để biết, nhưng cuối cùng, bạn có thể gọi một chức năng như thế này như bất kỳ chức năng nào khác. Trình biên dịch có thể sử dụng thông tin để tối ưu hóa, để đưa ra cảnh báo về mã chết, v.v. Vì vậy, không có lý do rất thuyết phục để có loại này, nhưng cũng không có lý do rất thuyết phục để tránh nó.

Trong nhiều ngôn ngữ, một hàm có thể trả về "void". Điều đó có nghĩa chính xác phụ thuộc vào ngôn ngữ. Trong C nó có nghĩa là hàm trả về không có gì. Trong Swift, điều đó có nghĩa là hàm trả về một đối tượng chỉ có một giá trị có thể và vì chỉ có một giá trị có thể mà giá trị đó lấy các bit bằng 0 và thực sự không yêu cầu bất kỳ mã nào. Trong cả hai trường hợp, điều đó không giống như "dưới cùng".

"dưới cùng" sẽ là một loại không có giá trị có thể. Nó không bao giờ có thể tồn tại. Nếu một hàm trả về "đáy", nó thực sự không thể trả về, vì không có giá trị nào của loại "đáy" mà nó có thể trả về.

Nếu một nhà thiết kế ngôn ngữ cảm thấy như vậy, thì không có lý do gì để không có loại đó. Việc thực hiện không khó (bạn có thể thực hiện chính xác như hàm trả về khoảng trống và được đánh dấu là "không trả về"). Bạn không thể trộn các con trỏ với các hàm trả về đáy với các con trỏ tới các hàm trả về khoảng trống, vì chúng không cùng loại).

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.