Tại sao một hàm có kiểu đa hình `forall t: Type, t-> t` là hàm nhận dạng?


18

Tôi chưa quen với lý thuyết ngôn ngữ lập trình. Tôi đã xem một số bài giảng trực tuyến trong đó người hướng dẫn tuyên bố rằng một hàm với kiểu đa hình forall t: Type, t->tlà danh tính, nhưng không giải thích được tại sao. Ai đó có thể giải thích cho tôi tại sao? Có thể là một bằng chứng của yêu cầu từ các nguyên tắc đầu tiên.


3
Tôi nghĩ câu hỏi này phải là một bản sao, nhưng tôi không thể tìm thấy nó. cs.stackexchange.com/questions/341/ Mạnh là một loại theo dõi. Tài liệu tham khảo tiêu chuẩn là Định lý miễn phí! bởi Phil Wadler.
Gilles 'SO- ngừng trở nên xấu xa'

1
Cố gắng xây dựng một hàm chung với loại này làm bất cứ điều gì khác. Bạn sẽ thấy rằng không có.
Bergi

@Bergi Có Tôi không thể tìm thấy bất kỳ ví dụ truy cập nào, nhưng vẫn không chắc chắn làm thế nào để chứng minh điều đó.
abhishek

Nhưng những gì bạn quan sát được khi bạn cố gắng tìm một? Tại sao bất kỳ nỗ lực nào bạn thực hiện không hoạt động?
Bergi

@Gilles Có thể bạn nhớ cs.stackexchange.com/q/19430/14663 ?
Bergi

Câu trả lời:


32

Điều đầu tiên cần lưu ý là điều này không nhất thiết phải đúng. Ví dụ, tùy thuộc vào ngôn ngữ mà một hàm có loại đó, ngoài chức năng nhận dạng, có thể: 1) lặp mãi mãi, 2) biến đổi một số trạng thái, 3) trả về null, 4) ném ngoại lệ, 5) thực hiện một số I / O, 6) rẽ nhánh một luồng để làm việc khác, 7) thực hiện call/ccshenanigans, 8) sử dụng một cái gì đó như Java Object.hashCode, 9) sử dụng sự phản chiếu để xác định xem loại đó có phải là số nguyên hay không và nếu nó, 10) sử dụng phản xạ để phân tích ngăn xếp cuộc gọi và làm một cái gì đó dựa trên bối cảnh mà nó được gọi là, 11) có thể nhiều thứ khác và chắc chắn là sự kết hợp tùy ý của những điều trên.

Vì vậy, thuộc tính dẫn đến điều này, tham số, là một thuộc tính của ngôn ngữ nói chung và có các biến thể mạnh hơn và yếu hơn của nó. Đối với nhiều phép tính chính thức được nghiên cứu trong lý thuyết loại, không có hành vi nào ở trên có thể xảy ra. Ví dụ, đối với Hệ thống F / phép tính lambda đa hình thuần túy, trong đó tham số đầu tiên được nghiên cứu, không có hành vi nào ở trên có thể xảy ra. Nó đơn giản là không có trường hợp ngoại lệ, nhà nước có thể thay đổi, null, call/cc, I / O, phản chiếu, và nó mạnh bình thường nên không thể lặp mãi mãi. Như Gilles đã đề cập trong một bình luận, bài báo Định lý miễn phí!bởi Phil Wadler là một giới thiệu tốt về chủ đề này và các tài liệu tham khảo của nó sẽ đi sâu hơn vào lý thuyết, đặc biệt là kỹ thuật quan hệ logic. Liên kết đó cũng liệt kê một số bài báo khác của Wadler về chủ đề tham số.

Vì tham số là một thuộc tính của ngôn ngữ, để chứng minh nó đòi hỏi trước tiên phải nói rõ ngôn ngữ và sau đó là một đối số tương đối phức tạp. Đối số không chính thức cho trường hợp cụ thể này giả sử chúng ta trong phép tính lambda đa hình là vì chúng ta không biết gì về việc tchúng ta không thể thực hiện bất kỳ thao tác nào trên đầu vào (ví dụ: chúng ta không thể tăng nó vì chúng ta không biết nếu đó là một số) hoặc tạo một giá trị của loại đó (cho tất cả những gì chúng ta biết t= Void, một loại không có giá trị nào cả). Cách duy nhất để tạo ra một giá trị của loại tlà trả lại giá trị được trao cho chúng ta. Không có hành vi khác là có thể. Một cách để thấy điều đó là sử dụng chuẩn hóa mạnh và chỉ ra rằng chỉ có một thuật ngữ dạng bình thường của loại này.


1
Làm thế nào mà Hệ thống F tránh được các vòng lặp vô hạn mà hệ thống loại không thể phát hiện? Điều đó được phân loại là không thể giải quyết trong trường hợp chung.
Joshua

2
@Joshua - bằng chứng không thể tiêu chuẩn cho vấn đề tạm dừng bắt đầu với giả định rằng có một vòng lặp vô hạn ở vị trí đầu tiên. Vì vậy, gọi nó để đặt câu hỏi tại sao Hệ thống F không có vòng lặp vô hạn là lý do vòng tròn. Nói rộng hơn, hệ thống F không được gần Turing hoàn chỉnh, vì vậy tôi nghi ngờ nó đáp ứng một trong các giả định về bằng chứng đó. Nó dễ dàng đủ yếu để máy tính chứng minh rằng tất cả các chương trình của nó chấm dứt (không có đệ quy, không có vòng lặp, chỉ rất yếu đối với các vòng lặp, v.v.).
Jonathan Cast

@Joshua: không thể giải quyết được trong trường hợp chung , điều này không loại trừ việc giải quyết nó trong nhiều trường hợp đặc biệt. Đặc biệt, mọi chương trình xảy ra là một hệ thống F được đánh máy tốt đã được chứng minh là dừng lại: có một bằng chứng thống nhất hoạt động cho tất cả các chương trình này. Rõ ràng, điều này có nghĩa là có các chương trình khác , không thể gõ vào hệ thống F ...
cody

15

Bằng chứng của yêu cầu khá phức tạp, nhưng nếu đó là những gì bạn thực sự muốn, bạn có thể xem bài viết gốc của Reynold về chủ đề này.

Ý tưởng chính là nó giữ cho các hàm đa hình tham số , trong đó phần thân của hàm đa hình là giống nhau cho tất cả các tức thời đơn hình của hàm. Trong một hệ thống như vậy, không có giả định nào có thể được đưa ra về loại tham số của loại đa hình và nếu giá trị duy nhất trong phạm vi có loại chung, không có gì phải làm với nó ngoài việc trả lại hoặc chuyển nó cho các chức năng khác mà bạn ' đã xác định, điều đó có thể lần lượt không làm gì ngoài việc trả lại hoặc vượt qua nó .. .etc. Vì vậy, cuối cùng, tất cả những gì bạn có thể làm là một số chuỗi chức năng nhận dạng trước khi trả về tham số.


8

Với tất cả những lời cảnh báo mà Derek đề cập và bỏ qua những nghịch lý xuất phát từ việc sử dụng lý thuyết tập hợp, hãy để tôi phác thảo một bằng chứng theo tinh thần của Reynold / Wadler.

Một chức năng của loại:

f :: forall t . t -> t

là một họ các hàm được lập chỉ mục bởi loại . tftt

Ý tưởng là, để chính thức xác định các hàm đa hình, chúng ta không nên coi các kiểu là tập hợp các giá trị, mà là quan hệ. Các loại cơ bản, như Intgây ra quan hệ bình đẳng - ví dụ: hai Intgiá trị có liên quan nếu chúng bằng nhau. Các hàm có liên quan nếu chúng ánh xạ các giá trị liên quan đến các giá trị liên quan. Trường hợp thú vị là các chức năng đa hình. Họ ánh xạ các loại liên quan đến các giá trị liên quan.

Trong trường hợp của chúng tôi, chúng tôi muốn thiết lập mối quan hệ giữa hai hàm đa hình và của loại:gfg

forall t . t -> t

Giả sử rằng loại có liên quan đến loại . Hàm đầu tiên ánh xạ loại thành một giá trị - ở đây, chính giá trị đó là hàm của loại . Hàm thứ hai ánh xạ loại sang giá trị khác của loại . Chúng tôi nói rằng có liên quan đến nếu các giá trị và có liên quan. Vì các giá trị này là các hàm, chúng có liên quan nếu chúng ánh xạ các giá trị liên quan đến các giá trị liên quan.t f s f s s s t g t t t f g f s g tstfsfssstgtttfgfsgt

Bước quan trọng là sử dụng định lý tham số của Reynold, nói rằng bất kỳ thuật ngữ nào cũng nằm trong mối quan hệ với chính nó. Trong trường hợp của chúng tôi, chức năng fcó liên quan đến chính nó. Nói cách khác, nếu scó liên quan đến t, cũng liên quan đến .f tfsft

Bây giờ chúng ta có thể chọn bất kỳ mối quan hệ giữa hai loại bất kỳ và áp dụng định lý này. Hãy chọn loại đầu tiên là loại đơn vị (), chỉ có một giá trị, cũng được gọi (). Chúng tôi sẽ giữ loại thứ hai ttùy ý nhưng không trống. Chúng ta hãy chọn một mối quan hệ giữa ()tchỉ đơn giản là một cặp ((), c), trong đó cmột số giá trị của loại t(một mối quan hệ chỉ là một tập hợp con của sản phẩm cartesian của các bộ). Định lý tham số cho chúng ta biết rằng phải liên quan đến . Họ phải ánh xạ các giá trị liên quan đến các giá trị liên quan. Hàm đầu tiên không có nhiều sự lựa chọn, nó phải ánh xạ giá trị duy nhất trở lại . Do đó, hàm thứ hai f t f (f()ft f t f t i d tf()()()ftphải ánh xạ ctới c(các giá trị duy nhất liên quan đến ()). Vì choàn toàn tùy ý, chúng tôi kết luận rằng là và, vì nó hoàn toàn tùy ý, là .ftidttfid

Bạn có thể tìm thêm chi tiết trong blog của tôi .


-2

EDIT: Một bình luận ở trên đã cung cấp phần còn thiếu. Một số người đang cố tình chơi với các ngôn ngữ chưa hoàn chỉnh. Tôi rõ ràng không quan tâm đến các ngôn ngữ như vậy. Một ngôn ngữ không thực sự hữu dụng có thể sử dụng được là một điều khó khăn để thiết kế. Toàn bộ phần còn lại của điều này mở rộng về những gì xảy ra khi cố gắng áp dụng các định lý này vào một ngôn ngữ đầy đủ.

Sai!

function f(a): forall t: Type, t->t
    function g(a): forall t: Type, t->t
       return (a is g) ? f : a
    return a is f ? g : a

trong đó istoán tử so sánh hai biến để nhận dạng tham chiếu. Đó là, chúng chứa cùng một giá trị. Không phải là một giá trị tương đương, cùng một giá trị. Các hàm fgtương đương theo một số định nghĩa nhưng chúng không giống nhau.

Nếu chức năng này được thông qua chính nó sẽ trả về một cái gì đó khác; nếu không, nó trả về đầu vào của nó. Một cái gì đó khác có cùng loại với chính nó do đó nó có thể được thay thế. Nói cách khác, fkhông phải là danh tính, bởi vì f(f)trả về g, trong khi danh tính sẽ trở lại f.

Đối với định lý để giữ nó phải giả sử khả năng vô lý để giảm

function cantor(n, <z, a>) : forall t: t: Type int, <int, t> -> <int, t>
    return n > 1 ? cantor((n % 2 > 0) ? (n + 1) : n / 2, <z + 1, a>) : <z, a>
return cantor(1000, <0, a>)[1]¹

Nếu bạn sẵn sàng cho rằng bạn có thể giả sử loại suy luận dễ dàng hơn nhiều có thể được xử lý.

Nếu chúng ta cố gắng hạn chế miền cho đến khi định lý được giữ, cuối cùng chúng ta sẽ phải hạn chế miền quá xa.

  • Chức năng thuần túy (không có trạng thái đột biến, không có IO). OK tôi có thể sống với điều đó. Rất nhiều thời gian chúng tôi muốn chạy bằng chứng về các chức năng.
  • Thư viện tiêu chuẩn trống. ồ
  • Không raisevà không exit. Bây giờ chúng tôi bắt đầu bị hạn chế.
  • Không có loại đáy.
  • Ngôn ngữ có một quy tắc cho phép trình biên dịch thu gọn đệ quy vô hạn bằng cách giả sử nó phải chấm dứt. Trình biên dịch được phép từ chối đệ quy vô hạn tầm thường.
  • Trình biên dịch được phép thất bại nếu được trình bày với một cái gì đó không thể được chứng minh bằng cách nào .² Bây giờ thư viện chuẩn không thể lấy các hàm làm đối số. Boo.
  • Không có nil. Điều này đang bắt đầu có vấn đề. Chúng tôi đã hết cách để đối phó với 1 / 0.³
  • Ngôn ngữ không thể thực hiện suy luận kiểu nhánh và không có ghi đè khi lập trình viên có thể chứng minh loại suy luận ngôn ngữ không thể. Điều này là khá xấu.

Sự tồn tại của cả hai ràng buộc cuối cùng đã làm tê liệt ngôn ngữ. Mặc dù Turing vẫn hoàn thành cách duy nhất để có được công việc có mục đích chung là mô phỏng một nền tảng bên trong diễn giải một ngôn ngữ với các yêu cầu lỏng lẻo hơn.

Nếu bạn nghĩ trình biên dịch có thể suy ra cái đó, hãy thử cái này

function fermat(z) : int -> int
    function pow(x, p)
        return p = 0 ? 1 : x * pow(x, p - 1)
    function f2(x, y, z) : int, int, int -> <int, int>
        left = pow(x, 5) + pow(y, 5)
        right = pow(z, 5)
        return left = right
            ? <x, y>
            : pow(x, 5) < right
                ? f2(x + 1, y, z)
                : pow(y, 5) < right
                    ? f2(2, y + 1, z)
                    : f2(2, 2, z + 1)
    return f2(2, 2, z)
function cantor(n, <z, a>) : forall t: t: Type int, <int, t> -> <int, t>
    return n > 1 ? cantor((n % 2 > 0) ? (n + 1) : n / 2, <z + 1, a>) : <z, a>
return cantor(fermat(3)[0], <0, a>)[1]

² Bằng chứng cho thấy trình biên dịch không thể làm điều này phụ thuộc vào chói mắt. Chúng tôi có thể sử dụng nhiều thư viện để đảm bảo trình biên dịch không thể nhìn thấy vòng lặp cùng một lúc. Ngoài ra, chúng ta luôn có thể xây dựng một cái gì đó mà chương trình sẽ hoạt động nhưng không thể được biên dịch vì trình biên dịch không thể thực hiện cảm ứng trong bộ nhớ khả dụng.

Ai đó nghĩ rằng bạn có thể có con số hoàn trả này mà không có loại chung chung tùy ý trả về con số không. Điều này trả một hình phạt khó chịu mà tôi đã thấy không có ngôn ngữ hiệu quả có thể trả nó.

function f(a, b, c): t: Type: t[],int,int->t
    return a[b/c]

không được biên dịch. Vấn đề cơ bản là lập chỉ mục mảng thời gian chạy không hoạt động nữa.


@Bergi: Tôi đã xây dựng một ví dụ mẫu.
Joshua

1
Hãy dành một chút thời gian để suy nghĩ về sự khác biệt giữa câu trả lời của bạn và hai câu hỏi còn lại. Câu mở đầu của Derek là thế giới Điều đầu tiên cần lưu ý là điều này không nhất thiết là đúng. Và sau đó anh ta giải thích những tính chất của một ngôn ngữ làm cho nó đúng. jmite cũng giải thích những gì làm cho nó đúng. Ngược lại, câu trả lời của bạn đưa ra một ví dụ bằng một ngôn ngữ không xác định (và ngôn ngữ không phổ biến) với lời giải thích bằng không. (Công cụ foilđịnh lượng là gì?) Điều này không hữu ích chút nào.
Gilles 'SO- ngừng trở nên xấu xa'

1
@DW: nếu a là f thì loại a là loại f cũng là loại g và do đó lỗi đánh máy sẽ vượt qua. Nếu một trình biên dịch thực sự loại bỏ nó, tôi sẽ sử dụng thời gian chạy mà các ngôn ngữ thực luôn có cho hệ thống kiểu tĩnh bị lỗi và nó sẽ không bao giờ bị lỗi khi chạy.
Joshua

2
Đó không phải là cách một máy đánh chữ tĩnh hoạt động. Nó không kiểm tra xem các loại có khớp với một đầu vào cụ thể không. Có các quy tắc loại cụ thể, nhằm đảm bảo rằng hàm sẽ đánh máy trên tất cả các đầu vào có thể. Nếu bạn yêu cầu sử dụng một typecast thì giải pháp này ít thú vị hơn nhiều. Tất nhiên nếu bạn bỏ qua hệ thống loại thì loại chức năng đảm bảo không có gì - không có gì bất ngờ cả!
DW

1
@DW: Bạn bỏ lỡ điểm. Có đủ thông tin cho trình kiểm tra kiểu tĩnh để chứng minh mã là loại an toàn nếu nó có trí thông minh để tìm nó.
Joshua
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.