Gõ phụ thuộc là gì?


82

Ai đó có thể giải thích cách gõ phụ thuộc cho tôi không? Tôi có ít kinh nghiệm về Haskell, Cayenne, Epigram hoặc các ngôn ngữ chức năng khác, vì vậy các thuật ngữ bạn có thể sử dụng càng đơn giản, tôi càng đánh giá cao nó!


Vì vậy, những gì chính xác bạn không hiểu về ví dụ như bài báo wikipedia?
Karl Knechtel

124
Vâng, bài viết mở đầu bằng các khối lambda, với tôi nghe giống như một loại thịt cừu nào đó. Sau đó, nó tiếp tục thảo luận về hệ thống λΠ2, và vì tôi không nói về người ngoài hành tinh nên tôi đã bỏ qua phần đó. Sau đó, tôi đọc về phép tính quy nạp, điều mà tình cờ dường như chẳng liên quan gì đến phép tính tích, truyền nhiệt hoặc xây dựng. Sau khi đưa ra bảng so sánh ngôn ngữ, bài viết kết thúc, và tôi còn bối rối hơn cả khi vào trang.
Nick

3
@Nick Đó là một vấn đề chung của Wikipedia. Tôi đã xem bình luận của bạn cách đây vài năm, và tôi đã nhớ nó kể từ đó. Tôi đang đánh dấu nó bây giờ.
Daniel H

Câu trả lời:


111

Hãy xem xét điều này: trong tất cả các ngôn ngữ lập trình phù hợp, bạn có thể viết các hàm, ví dụ:

def f(arg) = result

Ở đây, fnhận một giá trị argvà tính toán một giá trị result. Nó là một hàm từ giá trị thành giá trị.

Giờ đây, một số ngôn ngữ cho phép bạn xác định các giá trị đa hình (hay còn gọi là chung chung):

def empty<T> = new List<T>()

Ở đây, emptylấy một kiểu Tvà tính một giá trị. Nó là một chức năng từ loại đến giá trị.

Thông thường, bạn cũng có thể có các định nghĩa loại chung chung:

type Matrix<T> = List<List<T>>

Định nghĩa này nhận một kiểu và nó trả về một kiểu. Nó có thể được xem như là một chức năng từ loại đến loại.

Quá nhiều cho những gì ngôn ngữ thông thường cung cấp. Một ngôn ngữ được gọi là kiểu phụ thuộc nếu nó cũng cung cấp khả năng thứ 4, đó là xác định các hàm từ giá trị sang kiểu. Hay nói cách khác, tham số hóa định nghĩa kiểu trên một giá trị:

type BoundedInt(n) = {i:Int | i<=n}

Một số ngôn ngữ chính thống có một số dạng giả mạo của điều này mà bạn không nên nhầm lẫn. Ví dụ: trong C ++, các mẫu có thể nhận các giá trị làm tham số, nhưng chúng phải là hằng số thời gian biên dịch khi được áp dụng. Không phải như vậy trong một ngôn ngữ được đánh máy thực sự phụ thuộc. Ví dụ, tôi có thể sử dụng kiểu trên như thế này:

def min(i : Int, j : Int) : BoundedInt(j) =
  if i < j then i else j

Ở đây, kiểu kết quả của hàm phụ thuộc vào giá trị đối số thực tế j, do đó là thuật ngữ.


Không phải là BoundedIntví dụ thực sự là một loại tinh tế, mặc dù? Đó là 'khá gần gũi' nhưng không chính xác là loại 'kiểu phụ thuộc' mà Idris đề cập đầu tiên trong một hướng dẫn về dep.typing.
Narfanar

3
@Noein, các loại sàng lọc thực sự là một dạng đơn giản của các loại phụ thuộc.
Andreas Rossberg

21

Các kiểu phụ thuộc cho phép loại bỏ tập hợp các lỗi logic lớn hơn tại thời điểm biên dịch . Để minh họa điều này, hãy xem xét đặc điểm kỹ thuật sau về chức năng f:

Hàm fphải chỉ nhận số nguyên chẵn làm đầu vào.

Nếu không có các loại phụ thuộc, bạn có thể làm điều gì đó như sau:

def f(n: Integer) := {
  if  n mod 2 != 0 then 
    throw RuntimeException
  else
    // do something with n
}

Ở đây trình biên dịch không thể phát hiện xem ncó thực sự là chẵn hay không, nghĩa là, từ quan điểm của trình biên dịch, biểu thức sau là ok:

f(1)    // compiles OK despite being a logic error!

Chương trình này sẽ chạy và sau đó ném ra ngoại lệ trong thời gian chạy, tức là chương trình của bạn có lỗi logic.

Giờ đây, các kiểu phụ thuộc cho phép bạn diễn đạt nhiều hơn và sẽ cho phép bạn viết những thứ như sau:

def f(n: {n: Integer | n mod 2 == 0}) := {
  // do something with n
}

Đây nlà loại phụ thuộc {n: Integer | n mod 2 == 0}. Đọc to điều này có thể hữu ích khi

n là một thành viên của tập hợp các số nguyên sao cho mỗi số nguyên chia hết cho 2.

Trong trường hợp này, trình biên dịch sẽ phát hiện tại thời điểm biên dịch một lỗi logic trong đó bạn đã chuyển một số lẻ đến fvà sẽ ngăn chương trình được thực thi ngay từ đầu:

f(1)    // compiler error

Dưới đây là một ví dụ minh họa sử dụng các kiểu phụ thuộc vào đường dẫn Scala về cách chúng tôi có thể thử triển khai chức năng fđáp ứng yêu cầu như vậy:

case class Integer(v: Int) {
  object IsEven { require(v % 2 == 0) }
  object IsOdd { require(v % 2 != 0) }
}

def f(n: Integer)(implicit proof: n.IsEven.type) =  { 
  // do something with n safe in the knowledge it is even
}

val `42` = Integer(42)
implicit val proof42IsEven = `42`.IsEven

val `1` = Integer(1)
implicit val proof1IsOdd = `1`.IsOdd

f(`42`) // OK
f(`1`)  // compile-time error

Điều quan trọng là để ý cách giá trị nxuất hiện trong loại giá trị, proofcụ thể là n.IsEven.type:

def f(n: Integer)(implicit proof: n.IsEven.type)
      ^                           ^
      |                           |
    value                       value

Chúng tôi nói rằng kiểu n.IsEven.type phụ thuộc vào giá trị n do đó có thuật ngữ là kiểu phụ thuộc .


5
Nó xử lý như thế nào với giá trị ngẫu nhiên? Ví dụ, sẽ f(random())dẫn đến lỗi biên dịch?
Wong Jia Hau

5
Việc áp dụng fcho một số biểu thức sẽ yêu cầu trình biên dịch (có hoặc không có sự trợ giúp của bạn) cung cấp rằng biểu thức luôn là số chẵn và không có bằng chứng nào như vậy tồn tại random()(vì nó thực tế có thể là kỳ quặc), do đó f(random())sẽ không biên dịch được.
Matthijs

18

Nếu bạn tình cờ biết C ++, thật dễ dàng để cung cấp một ví dụ thúc đẩy:

Giả sử chúng ta có một số loại vùng chứa và hai trường hợp của chúng

typedef std::map<int,int> IIMap;
IIMap foo;
IIMap bar;

và xem xét đoạn mã này (bạn có thể cho rằng foo không trống):

IIMap::iterator i = foo.begin();
bar.erase(i);

Đây rõ ràng là rác (và có thể làm hỏng cấu trúc dữ liệu), nhưng nó sẽ kiểm tra kiểu tốt vì "trình lặp thành foo" và "trình lặp thành thanh" là cùng một kiểu, IIMap::iteratormặc dù chúng hoàn toàn không tương thích về mặt ngữ nghĩa.

Vấn đề là một loại iterator không nên phụ thuộc chỉ trên thùng sơn loại nhưng trên thực tế trên thùng đối tượng , tức là nó nên được một "loại thành viên không tĩnh":

foo.iterator i = foo.begin();
bar.erase(i);  // ERROR: bar.iterator argument expected

Một tính năng như vậy, khả năng thể hiện một kiểu (foo.iterator) phụ thuộc vào một thuật ngữ (foo), chính xác là ý nghĩa của việc gõ phụ thuộc.

Lý do bạn không thường xuyên nhìn thấy tính năng này là vì nó mở ra một lượng lớn sâu: bạn đột nhiên rơi vào tình huống mà tại thời điểm biên dịch, liệu hai loại có giống nhau hay không, bạn phải chứng minh hai biểu thức là tương đương (sẽ luôn mang lại cùng một giá trị trong thời gian chạy). Do đó, nếu bạn so sánh danh sách các ngôn ngữ được đánh máy phụ thuộc của wikipedia với danh sách các trình duyệt định lý của nó, bạn có thể nhận thấy một điểm tương đồng đáng ngờ. ;-)


4

Trích dẫn sách Các loại và Ngôn ngữ Lập trình (30.5):

Phần lớn cuốn sách này liên quan đến việc hình thức hóa các cơ chế trừu tượng của nhiều loại khác nhau. Trong phép tính lambda được nhập đơn giản, chúng tôi đã chính thức hóa hoạt động lấy một số hạng và trừu tượng hóa một thuật ngữ con, tạo ra một hàm sau này có thể được khởi tạo bằng cách áp dụng nó cho các thuật ngữ khác nhau. Trong Hệ thốngF , chúng tôi đã xem xét hoạt động lấy một thuật ngữ và trừu tượng hóa một kiểu, tạo ra một thuật ngữ có thể được khởi tạo bằng cách áp dụng nó cho nhiều kiểu khác nhau. Trongλω, chúng tôi tóm tắt lại các cơ chế của phép tính lambda được nhập đơn giản là “một cấp”, lấy một kiểu và trừu tượng hóa biểu thức con để có được một toán tử kiểu mà sau này có thể được khởi tạo bằng cách áp dụng nó cho các kiểu khác nhau. Một cách thuận tiện để suy nghĩ về tất cả các dạng trừu tượng này là về họ các biểu thức, được lập chỉ mục bởi các biểu thức khác. Một trừu tượng lambda thông thường λx:T1.t2là một họ các thuật ngữ [x -> s]t1được lập chỉ mục bởi các thuật ngữ s. Tương tự, một kiểu trừu tượng hóa λX::K1.t2là một họ thuật ngữ được lập chỉ mục theo kiểu và toán tử kiểu là họ kiểu được lập chỉ mục theo kiểu.

  • λx:T1.t2 họ thuật ngữ được lập chỉ mục theo thuật ngữ

  • λX::K1.t2 họ thuật ngữ được lập chỉ mục theo loại

  • λX::K1.T2 họ các loại được lập chỉ mục theo các loại

Nhìn vào danh sách này, rõ ràng là có một khả năng mà chúng tôi vẫn chưa xem xét: các họ kiểu được lập chỉ mục theo thuật ngữ. Hình thức trừu tượng này cũng đã được nghiên cứu rộng rãi, dưới sự đánh giá của các kiểu phụ thuộ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.