Sự đánh đổi cho kiểu suy luận là gì?


29

Dường như tất cả các ngôn ngữ lập trình mới hoặc ít nhất là những ngôn ngữ đã trở thành loại suy luận sử dụng phổ biến. Ngay cả Javascript cũng có kiểu và suy luận kiểu mặc dù các cách triển khai khác nhau (Acscript, typecript, v.v.). Nó có vẻ tuyệt vời đối với tôi nhưng tôi tự hỏi liệu có bất kỳ sự đánh đổi nào hay tại sao chúng ta hãy nói Java hoặc các ngôn ngữ tốt cũ không có kiểu suy luận

  • Khi khai báo một biến trong Go mà không chỉ định loại của nó (sử dụng var không có kiểu hoặc cú pháp: =), kiểu của biến được suy ra từ giá trị ở phía bên tay phải.
  • D cho phép viết các đoạn mã lớn mà không cần chỉ định các loại, như các ngôn ngữ động. Mặt khác, suy luận tĩnh suy ra các loại và các thuộc tính mã khác, mang lại điều tốt nhất cho cả thế giới tĩnh và thế giới động.
  • Công cụ suy luận kiểu trong Rust khá thông minh. Nó thực hiện nhiều hơn là nhìn vào loại giá trị r trong quá trình khởi tạo. Nó cũng xem cách biến được sử dụng sau đó để suy ra kiểu của nó.
  • Swift sử dụng suy luận kiểu để tìm ra loại thích hợp. Kiểu suy luận cho phép trình biên dịch tự động suy ra loại biểu thức cụ thể khi nó biên dịch mã của bạn, chỉ bằng cách kiểm tra các giá trị bạn cung cấp.

3
Trong C #, các hướng dẫn chung nói không phải lúc nào cũng sử dụng varvì đôi khi có thể làm tổn thương khả năng đọc.
Mephy 3/2/2015

5
"... Hoặc tại sao chúng ta nói Java hoặc các ngôn ngữ tốt cũ không có suy luận kiểu" Những lý do có thể mang tính lịch sử; ML xuất hiện 1 năm sau C theo Wikipedia và nó có kiểu suy luận. Java đã cố gắng thu hút các nhà phát triển C ++ . C ++ bắt đầu như một phần mở rộng của C, và mối quan tâm chính của C là trở thành một trình bao bọc di động so với lắp ráp và dễ biên dịch. Đối với bất cứ điều gì nó có giá trị, tôi đã đọc rằng phân nhóm làm cho suy luận kiểu không thể giải quyết được trong trường hợp chung là tốt.
Doval

3
@Doval Scala dường như làm rất tốt công việc suy luận các loại, đối với một ngôn ngữ hỗ trợ kế thừa kiểu phụ. Nó không tốt như bất kỳ ngôn ngữ gia đình ML nào, nhưng có lẽ nó tốt như bạn có thể yêu cầu, với thiết kế của ngôn ngữ.
KChaloux

12
Thật đáng để phân biệt giữa loại trừ (đơn hướng, như C # varvà C ++ auto) và loại suy luận (hai chiều, như Haskell let). Trong trường hợp trước, loại tên có thể được suy ra từ trình khởi tạo của nó, chỉ sử dụng tên của nó phải tuân theo loại tên đó. Trong trường hợp sau, loại tên có thể được suy ra từ cách sử dụng của nó, cũng rất hữu ích ở chỗ bạn có thể viết đơn giản []cho một chuỗi trống, bất kể loại phần tử hoặc newEmptyMVartham chiếu có thể thay đổi null mới, bất kể tham chiếu nào kiểu.
Jon Purdy

Kiểu suy luận có thể cực kỳ phức tạp để thực hiện, đặc biệt là hiệu quả. Mọi người không muốn sự phức tạp biên dịch của họ phải chịu sự thổi phồng theo cấp số nhân từ các biểu thức phức tạp hơn. Đọc thú vị: cocoawithlove.com/blog/2016/07/12/type-checker-issues.html
Alexander - Tái lập Monica

Câu trả lời:


43

Hệ thống loại của Haskell hoàn toàn không thể tìm thấy (bỏ qua đệ quy đa hình, một số phần mở rộng ngôn ngữ nhất định và hạn chế tính đơn hình đáng sợ ), nhưng các lập trình viên vẫn thường xuyên cung cấp các chú thích loại trong mã nguồn ngay cả khi họ không cần. Tại sao?

  1. Loại chú thích phục vụ như tài liệu . Điều này đặc biệt đúng với các loại biểu cảm như của Haskell. Đặt tên của hàm và loại của hàm, bạn thường có thể đoán khá rõ chức năng đó làm gì, đặc biệt khi hàm này là đa hình tham số.
  2. Chú thích loại có thể thúc đẩy sự phát triển . Viết một chữ ký loại trước khi bạn viết phần thân của hàm cảm thấy giống như sự phát triển dựa trên kiểm tra. Theo kinh nghiệm của tôi, một khi bạn tạo một hàm Haskell, nó thường hoạt động lần đầu tiên. (Tất nhiên, điều này không làm giảm nhu cầu kiểm tra tự động!)
  3. Các loại rõ ràng có thể giúp bạn kiểm tra các giả định của bạn . Khi tôi đang cố gắng để hiểu một số mã đã hoạt động, tôi thường tiêu hóa nó với những gì tôi tin là các chú thích đúng loại. Nếu mã vẫn biên dịch tôi biết tôi đã hiểu nó. Nếu không, tôi đọc thông báo lỗi.
  4. Chữ ký loại cho phép bạn chuyên các chức năng đa hình . Rất hiếm khi, một API có tính biểu cảm hoặc hữu ích hơn nếu các chức năng nhất định không phải là đa hình. Trình biên dịch sẽ không phàn nàn nếu bạn cung cấp cho hàm một loại ít tổng quát hơn sẽ được suy ra. Ví dụ kinh điển là map :: (a -> b) -> [a] -> [b]. Dạng tổng quát hơn của nó ( fmap :: Functor f => (a -> b) -> f a -> f b) áp dụng cho tất cả các Functors, không chỉ các danh sách. Nhưng nó đã cảm thấy rằng mapsẽ dễ hiểu hơn cho người mới bắt đầu, vì vậy nó sống cùng với người anh lớn của mình.

Nhìn chung, nhược điểm của một hệ thống gõ tĩnh nhưng không thể hiểu được cũng giống như nhược điểm của việc gõ tĩnh nói chung, một cuộc thảo luận mòn mỏi trên trang web này và các vấn đề khác (Googling "nhược điểm gõ tĩnh" sẽ giúp bạn có hàng trăm của các trang của cuộc chiến lửa). Tất nhiên, một số nhược điểm đã nói được cải thiện bởi số lượng chú thích loại nhỏ hơn trong một hệ thống không thể chịu đựng được. Thêm vào đó, suy luận kiểu có lợi thế riêng của nó: phát triển dựa vào lỗ sẽ không thể thực hiện được nếu không có suy luận kiểu.

Java * chứng minh rằng một ngôn ngữ yêu cầu quá nhiều chú thích loại gây khó chịu, nhưng với quá ít bạn mất đi những lợi thế mà tôi đã mô tả ở trên. Các ngôn ngữ có loại suy luận từ chối đánh vào sự cân bằng dễ chịu giữa hai thái cực.

* Ngay cả Java, vật tế thần tuyệt vời đó, thực hiện một số lượng suy luận kiểu cục bộ nhất định . Trong một tuyên bố như Map<String, Integer> = new HashMap<>();, bạn không phải chỉ định loại chung của hàm tạo. Mặt khác, các ngôn ngữ kiểu ML thường không thể hiểu được trên toàn cầu .


2
Nhưng bạn không phải là người mới bắt đầu;) Bạn phải có hiểu biết về các loại lớp và loại trước khi bạn thực sự "có được" Functor. Danh sách và mapcó nhiều khả năng quen thuộc với Haskeller không dày dạn.
Benjamin Hodgson

1
Bạn có coi C # varlà một ví dụ về suy luận kiểu không?
Benjamin Hodgson

1
Có, C # varlà một ví dụ thích hợp.
Doval 3/2/2015

1
Trang web đã gắn cờ cuộc thảo luận này quá dài nên đây sẽ là câu trả lời cuối cùng của tôi. Trước đây trình biên dịch phải xác định loại x; sau này không có loại nào để xác định, tất cả các loại đều được biết và bạn chỉ cần kiểm tra xem biểu thức đó có hợp lý không. Sự khác biệt trở nên quan trọng hơn khi bạn di chuyển qua các ví dụ tầm thường và xđược sử dụng ở nhiều nơi; trình biên dịch sau đó phải kiểm tra chéo các vị trí xđược sử dụng để xác định 1) có thể gán xmột loại sao cho mã sẽ gõ kiểm tra không? 2) Nếu là, loại chung nhất chúng ta có thể gán nó là gì?
Doval 3/2/2015

1
Lưu ý rằng new HashMap<>();cú pháp chỉ được thêm vào trong Java 7 và lambdas trong Java 8 cho phép thực hiện khá nhiều suy luận kiểu "thực".
Michael Borgwardt

8

Trong C #, kiểu suy luận xảy ra tại thời gian biên dịch, do đó chi phí thời gian chạy bằng không.

Là một vấn đề về phong cách, varđược sử dụng cho các tình huống bất tiện hoặc không cần thiết để chỉ định loại thủ công. Linq là một trong những tình huống như vậy. Cái khác là:

var s = new SomeReallyLongTypeNameWith<Several, Type, Parameters>(andFormal, parameters);

không có điều đó bạn sẽ lặp lại tên loại thực sự dài của bạn (và các tham số loại) chứ không chỉ đơn giản là nói var.

Sử dụng tên thực của loại khi rõ ràng sẽ cải thiện độ rõ của mã.

Có một số tình huống không thể sử dụng suy luận kiểu, chẳng hạn như khai báo biến thành viên có giá trị được đặt tại thời điểm xây dựng hoặc khi bạn thực sự muốn intellisense hoạt động chính xác (IDE của Hackerrank sẽ không kích hoạt thành viên của biến trừ khi bạn khai báo loại rõ ràng) .


16
"Trong C #, kiểu suy luận xảy ra tại thời gian biên dịch, do đó chi phí thời gian chạy bằng không." Kiểu suy luận xảy ra tại thời gian biên dịch theo định nghĩa, vì vậy đó là trường hợp trong mọi ngôn ngữ.
Doval

Càng nhiều càng tốt.
Robert Harvey

1
@Doval có thể thực hiện suy luận kiểu trong quá trình biên dịch JIT, rõ ràng có chi phí thời gian chạy.
Jules

6
@Jules Nếu bạn đã chạy chương trình thì nó đã được kiểm tra rồi; không còn gì để suy luận Những gì bạn đang nói về thường không được gọi là suy luận kiểu, mặc dù tôi không chắc thuật ngữ thích hợp là gì.
Doval 3/2/2015

7

Câu hỏi hay!

  1. Vì loại này không được chú thích rõ ràng, đôi khi nó có thể làm cho mã khó đọc hơn - dẫn đến nhiều lỗi hơn. Sử dụng đúng cách nó tất nhiên làm cho mã sạch hơn và dễ đọc hơn . Nếu bạn là một người bi quan và nghĩ rằng hầu hết các lập trình viên đều xấu (hoặc làm việc trong đó hầu hết các lập trình viên đều xấu), đây sẽ là một mất mát ròng.
  2. Trong khi thuật toán suy luận kiểu tương đối đơn giản, nó không miễn phí . Loại điều này làm tăng thời gian biên dịch một chút.
  3. Vì loại này không được chú thích rõ ràng, IDE của bạn cũng không thể đoán được những gì bạn đang cố gắng làm, gây hại cho quá trình tự động hoàn thành và các trình trợ giúp tương tự trong quá trình khai báo.
  4. Kết hợp với tình trạng quá tải chức năng, đôi khi bạn có thể gặp phải tình huống trong đó thuật toán suy luận kiểu không thể quyết định đường dẫn nào sẽ dẫn đến chú thích kiểu đúc xấu hơn. (Điều này xảy ra khá nhiều với cú pháp hàm ẩn danh của C # chẳng hạn).

Và có nhiều ngôn ngữ bí truyền không thể làm được sự kỳ lạ của chúng mà không có chú thích kiểu rõ ràng. Cho đến nay, không có cái nào tôi biết là phổ biến / phổ biến / đủ hứa hẹn để đề cập ngoại trừ việc thông qua.


1
tự động hoàn thành không đưa ra một suy nghĩ về loại suy luận. Không quan trọng là loại đã được quyết định như thế nào, chỉ có loại có. Và các vấn đề với lambdas của C # không liên quan gì đến suy luận, đó là bởi vì lambdas là đa hình nhưng hệ thống loại không thể đối phó với nó - không liên quan gì đến suy luận.
DeadMG

2
@deadmg - chắc chắn, một khi biến được khai báo thì không có vấn đề gì, nhưng trong khi bạn gõ khai báo biến, nó không thể thu hẹp các tùy chọn của phía bên phải sang loại khai báo vì không có loại khai báo.
Telastyn 3/2/2015

@deadmg - đối với lambdas, tôi không hiểu lắm về ý của bạn, nhưng tôi khá chắc chắn rằng nó không hoàn toàn chính xác dựa trên sự hiểu biết của tôi về cách mọi thứ hoạt động. Ngay cả một cái gì đó đơn giản như var foo = x => x;thất bại bởi vì ngôn ngữ cần phải suy luận xở đây và không có gì để tiếp tục. Khi lambdas được xây dựng, chúng được xây dựng dưới dạng các đại biểu được gõ rõ ràng Func<int, int> foo = x => xđược tích hợp vào CIL giống như Func<int, int> foo = new Func<int, int>(x=>x);nơi lambda được xây dựng dưới dạng hàm được gõ rõ ràng. Đối với tôi, suy ra loại xlà một phần và phần của loại suy luận ...
Telastyn

3
@Telastyn Không đúng khi ngôn ngữ không có gì để tiếp tục. Tất cả thông tin cần thiết để nhập biểu thức x => xđược chứa trong chính lambda - nó không đề cập đến bất kỳ biến nào trong phạm vi xung quanh. Bất kỳ lành mạnh ngôn ngữ chức năng sẽ suy ra một cách chính xác rằng kiểu của nó là "cho tất cả các loại a, a -> a". Tôi nghĩ điều mà DeadMG đang cố gắng nói là hệ thống loại của C # thiếu thuộc tính loại chính , điều đó có nghĩa là luôn có thể tìm ra loại chung nhất cho bất kỳ biểu thức nào. Đó là một tài sản rất dễ dàng để phá hủy.
Doval

@Doval - nâng cao ... không có thứ gọi là đối tượng hàm chung trong C #, mà tôi nghĩ là khác biệt tinh tế so với những gì cả hai bạn đang nói về ngay cả khi nó dẫn đến cùng một rắc rối.
Telastyn 3/2/2015

4

Nó có vẻ tuyệt vời đối với tôi nhưng tôi tự hỏi liệu có bất kỳ sự đánh đổi nào hay tại sao chúng ta hãy nói Java hoặc các ngôn ngữ tốt cũ không có kiểu suy luận

Java xảy ra là ngoại lệ chứ không phải là quy tắc ở đây. Ngay cả C ++ (mà tôi tin là đủ điều kiện là "ngôn ngữ cũ tốt" :)) hỗ trợ suy luận kiểu với autotừ khóa kể từ tiêu chuẩn C ++ 11. Nó không chỉ hoạt động với khai báo biến, mà còn là kiểu trả về hàm, đặc biệt tiện dụng với một số hàm mẫu phức tạp.

Gõ ngầm và suy luận kiểu có nhiều trường hợp sử dụng tốt, và cũng có một số trường hợp sử dụng mà bạn thực sự không nên làm. Điều này đôi khi là vấn đề của hương vị, và cũng là chủ đề tranh luận.

Nhưng chắc chắn có những trường hợp sử dụng tốt, chắc chắn là một lý do chính đáng cho bất kỳ ngôn ngữ nào để thực hiện nó. Nó cũng không phải là một tính năng khó thực hiện, không có hình phạt thời gian chạy và không ảnh hưởng đáng kể đến thời gian biên dịch.

Tôi không thấy một nhược điểm thực sự trong việc tạo cơ hội cho nhà phát triển sử dụng suy luận kiểu.

Một số người trả lời reasond đôi khi cách gõ rõ ràng là tốt, và họ chắc chắn là đúng. Nhưng không hỗ trợ gõ ngầm sẽ có nghĩa là một ngôn ngữ luôn luôn thực thi kiểu gõ rõ ràng.

Vì vậy, nhược điểm thực sự là khi một ngôn ngữ không hỗ trợ gõ ngầm, bởi vì với điều này, nó nói rằng không có lý do chính đáng nào để nhà phát triển sử dụng nó, điều đó không đúng.


1

Sự khác biệt chính giữa hệ thống suy luận kiểu Hindley-Milner và suy luận kiểu kiểu Go là hướng của luồng thông tin. Trong HM, nhập thông tin chuyển tiếp và ngược thông qua thống nhất; trong Go, nhập thông tin chỉ chuyển tiếp: nó chỉ tính toán chuyển tiếp thay thế.

Suy luận kiểu HM là một sự đổi mới tuyệt vời hoạt động tốt với các ngôn ngữ chức năng được đánh máy đa hình, nhưng các tác giả của Go có thể sẽ tranh luận rằng nó cố gắng làm quá nhiều:

  1. Thực tế là thông tin chảy cả về phía trước và phía sau có nghĩa là suy luận kiểu HM là một vấn đề rất phi tập trung; trong trường hợp không có chú thích kiểu, mỗi dòng mã trong chương trình của bạn có thể góp phần vào việc gõ một dòng mã. Nếu bạn chỉ có sự thay thế về phía trước, bạn biết rằng nguồn của lỗi loại phải nằm trong mã có trước lỗi của bạn; không phải là mã đi sau

  2. Với suy luận kiểu HM, bạn có xu hướng suy nghĩ hạn chế: khi bạn sử dụng một loại, bạn hạn chế những loại có thể có. Vào cuối ngày, có thể có một số biến loại hoàn toàn không bị ràng buộc. Cái nhìn sâu sắc của suy luận kiểu HM là, những kiểu đó thực sự không quan trọng, và do đó chúng được tạo thành các biến đa hình. Tuy nhiên, tính đa hình bổ sung này có thể là không mong muốn vì một số lý do. Đầu tiên, như một số người đã chỉ ra, tính đa hình bổ sung này có thể là không mong muốn: HM kết luận một loại không có thật, đa hình cho một số mã không có thật và dẫn đến các lỗi lạ sau này. Thứ hai, khi một kiểu được để lại đa hình, điều này có thể có hậu quả cho hành vi thời gian chạy. Ví dụ: kết quả trung gian đa hình quá mức là lý do tại sao 'hiển thị. đọc 'được coi là mơ hồ trong Haskell; như một ví dụ khác,


2
Thật không may, đây có vẻ là một câu trả lời tốt cho một câu hỏi khác. OP đang hỏi về "suy luận kiểu đi" so với các ngôn ngữ hoàn toàn không có suy luận kiểu nào, không phải là "kiểu đi" so với HM.
Ixrec 22/03/2016

-2

Nó làm tổn thương khả năng đọc.

Đối chiếu

var stuff = someComplexFunction()

đấu với

List<BusinessObject> stuff = someComplexFunction()

Đó đặc biệt là một vấn đề khi đọc bên ngoài một IDE như trên github.


4
điều này dường như không cung cấp bất cứ điều gì đáng kể qua các điểm được thực hiện và giải thích trong 6 câu trả lời trước
gnat

Nếu bạn sử dụng tên riêng thay vì "không thể đọc được", thì varcó thể được sử dụng mà không làm tổn thương khả năng đọc. var objects = GetBusinessObjects();
Fabio

Ok nhưng sau đó bạn đang rò rỉ hệ thống loại vào tên biến. Nó giống như ký hiệu của Lynn. Sau khi tái cấu trúc, nhiều khả năng bạn sẽ kết thúc với các tên không khớp với mã. Nói chung, phong cách chức năng đẩy bạn mất đi những lợi thế không gian tên tự nhiên mà các lớp cung cấp. Vì vậy, bạn kết thúc với nhiều SquareArea () thay vì Square.area ().
miguel
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.