Là đa hình tham số cấp cao hơn có hữu ích?


16

Tôi khá chắc chắn rằng mọi người đều quen thuộc với các phương thức chung của biểu mẫu:

T DoSomething<T>(T item)

Hàm này còn được gọi là đa hình tham số (PP), cụ thể PP cấp 1 .

Giả sử phương thức này có thể được biểu diễn bằng một đối tượng hàm có dạng:

<T> : T -> T

Nghĩa là, <T>nó có một tham số loại và T -> Tcó nghĩa là nó lấy một tham số loại Tvà trả về một giá trị cùng loại.

Sau đó, sau đây sẽ là một hàm PP hạng 2:

(<T> : T -> T) -> int 

Hàm không có tham số kiểu chính nó, nhưng có một hàm lấy tham số kiểu. Bạn có thể tiếp tục lặp đi lặp lại điều này, làm cho việc lồng ngày càng sâu hơn, nhận được PP có thứ hạng cao hơn và cao hơn.

Tính năng này thực sự hiếm trong số các ngôn ngữ lập trình. Ngay cả Haskell cũng không cho phép nó theo mặc định.

Nó khá hữu ích? Nó có thể mô tả các hành vi khó mô tả khác không?

Ngoài ra, nó có ý nghĩa gì đối với một cái gì đó là điềm tĩnh ? (trong ngữ cảnh này)


1
Thật thú vị, TypeScript là một ngôn ngữ chính với sự hỗ trợ PP cấp bậc đầy đủ. Ví dụ: dưới đây là mã let sdff = (g : (f : <T> (e : T) => void) => void) => {}
TypeScript

Câu trả lời:


11

Nói chung, bạn sử dụng đa hình bậc cao hơn khi bạn muốn callee có thể chọn giá trị của một tham số loại, thay vì người gọi . Ví dụ:

f :: (forall a. Show a => a -> Int) -> (Int, Int)
f g = (g "one", g 2)

Bất kỳ chức năng gnào tôi chuyển đến điều này fphải có thể cung cấp cho tôi Inttừ một giá trị của một loại nào đó, trong đó điều duy nhất gbiết về loại đó là nó có một thể hiện của Show. Vì vậy, đây là kosher:

f (length . show)
f (const 42)

Nhưng đây không phải là:

f length
f succ

Một ứng dụng đặc biệt hữu ích là sử dụng phạm vi của các loại để thực thi phạm vi giá trị . Giả sử chúng ta có một đối tượng của kiểu Action<T>, đại diện cho một hành động mà chúng ta có thể chạy để tạo ra kết quả của loại T, chẳng hạn như tương lai hoặc gọi lại.

T runAction<T>(Action<T>)

runAction :: forall a. Action a -> a

Bây giờ, giả sử rằng chúng ta cũng có một đối tượng Actioncó thể phân bổ Resource<T>các đối tượng:

Action<Resource<T>> newResource<T>(T)

newResource :: forall a. a -> Action (Resource a)

Chúng tôi muốn thực thi rằng các tài nguyên đó chỉ được sử dụng bên trong Actionnơi chúng được tạo và không được chia sẻ giữa các hành động khác nhau hoặc các hoạt động khác nhau của cùng một hành động, để các hành động có tính quyết định và có thể lặp lại.

Chúng ta có thể sử dụng các loại được xếp hạng cao hơn để thực hiện điều này bằng cách thêm một tham số Scho ResourceActioncác loại, đó là hoàn toàn trừu tượng, nó đại diện cho phạm vi của phạm vi Action. Bây giờ chữ ký của chúng tôi là:

T run<T>(<S> Action<S, T>)
Action<S, Resource<S, T>> newResource<T>(T)

runAction :: forall a. (forall s. Action s a) -> a
newResource :: forall s a. a -> Action s (Resource s a)

Bây giờ khi chúng tôi đưa ra runActionmột Action<S, T>, chúng tôi chắc chắn rằng vì tham số phạm vi phạm vi của chế độ Sđa hình, nó không thể thoát khỏi cơ thể của runActionbất kỳ giá trị nào của một loại sử dụng Snhư Resource<S, int>vậy cũng không thể thoát!

(Trong Haskell, đây được gọi là STđơn nguyên, nơi runActionđược gọi runST, Resourceđược gọi STRefnewResourceđược gọi newSTRef.)


Các STđơn nguyên là một ví dụ rất thú vị. Bạn có thể đưa ra một số ví dụ nữa về khi đa hình cấp cao hơn sẽ hữu ích?
GregRos

@GregRos: Nó cũng tiện dụng với các tồn tại. Trong Haxl , chúng tôi đã có một dạng tồn tại data Fetch d = forall a. Fetch (d a) (MVar a), đó là một cặp yêu cầu đối với nguồn dữ liệu dvà một vị trí để lưu trữ kết quả. Kết quả và vị trí phải có loại phù hợp, nhưng loại đó bị ẩn, do đó bạn có thể có một danh sách các yêu cầu không đồng nhất với cùng một nguồn dữ liệu. Bây giờ bạn có thể sử dụng đa hình bậc cao hơn để viết một hàm tìm nạp tất cả các yêu cầu, được cung cấp một hàm tìm nạp một : fetch :: (forall a. d a -> IO a) -> [Fetch d] -> IO ().
Jon Purdy

8

Đa hình cấp bậc cao hơn là vô cùng hữu ích. Trong Hệ thống F (ngôn ngữ cốt lõi của các ngôn ngữ FP đã nhập mà bạn quen thuộc), đây là điều cần thiết để thừa nhận "mã hóa Giáo hội đã nhập", đây thực sự là cách Hệ thống F thực hiện lập trình. Không có những thứ này, hệ thống F hoàn toàn vô dụng.

Trong Hệ thống F, chúng tôi định nghĩa các số là

Nat = forall c. (c -> c) -> c -> c

Ngoài ra có loại

plus : Nat -> Nat -> Nat
plus l r = Λ t. λ (s : t -> t). λ (z : t). l s (r s z)

đó là loại thứ hạng cao hơn ( forall c.xuất hiện bên trong những mũi tên đó).

Điều này đến ở những nơi khác quá. Ví dụ: nếu bạn muốn chỉ ra rằng một tính toán là một kiểu chuyển tiếp tiếp tục phù hợp (google "mã hóa haskell") thì bạn sẽ đúng như vậy

type CPSed A = forall c. (A -> c) -> c

Ngay cả khi nói về một loại không có người ở trong Hệ thống F đòi hỏi tính đa hình cấp bậc cao hơn

type Void = forall a. a 

Dài và ngắn của việc này, viết một hàm trong một hệ thống kiểu thuần túy (Hệ thống F, CoC) đòi hỏi tính đa hình xếp hạng cao hơn nếu chúng ta muốn xử lý bất kỳ dữ liệu thú vị nào.

Trong Hệ thống F nói riêng, các bảng mã này cần phải là "bắt buộc". Điều này có nghĩa là forall a.định lượng trên tất cả các loại . Điều này cực kỳ quan trọng bao gồm chính loại chúng tôi đang xác định. Trong forall a. ađó athực sự có thể đứng cho forall a. amột lần nữa! Trong các ngôn ngữ như ML, đây không phải là trường hợp, chúng được gọi là "dự đoán" do một biến loại chỉ định lượng trên tập hợp các loại mà không có bộ định lượng (được gọi là đơn hình). Định nghĩa của chúng ta về plusimpredicativity cần cũng bởi vì chúng tôi instantiated ctrong l : Natđược Nat!

Cuối cùng, tôi muốn đề cập đến một lý do cuối cùng trong đó bạn thích cả tính đa hình và tính đa hình cấp bậc cao hơn ngay cả trong một ngôn ngữ với các kiểu đệ quy tùy ý (không giống như Hệ thống F). Trong Haskell, có một đơn vị cho các hiệu ứng được gọi là "đơn nguyên chủ đề trạng thái". Ý tưởng là đơn nguyên chủ đề trạng thái cho phép bạn thay đổi mọi thứ nhưng yêu cầu phải thoát khỏi nó rằng kết quả của bạn không phụ thuộc vào bất cứ điều gì có thể thay đổi. Điều này có nghĩa là các tính toán ST hoàn toàn có thể quan sát được. Để thực thi yêu cầu này, chúng tôi sử dụng đa hình cấp bậc cao hơn

runST :: forall a. (forall s. ST s a) -> a

Ở đây bằng cách đảm bảo rằng abị ràng buộc bên ngoài phạm vi mà chúng tôi giới thiệu s, chúng tôi biết rằng đó alà viết tắt của một loại hình không phù hợp s. Chúng tôi sử dụng sđể tối ưu hóa tất cả những thứ có thể thay đổi trong luồng trạng thái cụ thể đó để chúng tôi biết rằng anó độc lập với những thứ có thể thay đổi và do đó không có gì thoát khỏi phạm vi STtính toán đó ! Một ví dụ tuyệt vời về việc sử dụng các loại để loại trừ các chương trình không thành công.

Nhân tiện, nếu bạn thích tìm hiểu về lý thuyết loại, tôi khuyên bạn nên đầu tư vào một hoặc hai cuốn sách hay. Thật khó để học thứ này theo bit và miếng. Tôi muốn đề xuất một trong những cuốn sách của Pierce hoặc Harper về lý thuyết PL nói chung (và một số yếu tố của lý thuyết loại). Cuốn sách "Các chủ đề nâng cao về các loại và ngôn ngữ lập trình" cũng bao gồm một lượng lớn lý thuyết loại. Cuối cùng, "Lập trình trong lý thuyết loại của Martin Lof" là một giải thích rất tốt về lý thuyết loại cường độ mà Martin Lof đã vạch ra.


Cảm ơn vì lời giới thiệu của bạn. Tôi sẽ tìm chúng. Chủ đề này thực sự thú vị và tôi mong muốn một số khái niệm hệ thống loại tiên tiến hơn sẽ được chấp nhận bởi nhiều ngôn ngữ lập trình hơn. Họ cung cấp cho bạn nhiều sức mạnh biểu cảm hơn.
GregRos
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.