19

Tôi đang cố gắng hãnh diện các single đơn giản của Haskell.Haskell singletons: Chúng ta có thể đạt được gì với SNat

Trong giấy Dependently Typed Programming with Singletons và trong bài viết trên blog của ông singletons v0.9 Released! Richard Eisenberg định nghĩa kiểu dữ liệu Nat trong đó xác định các số tự nhiên với các tiên đề Peano:

data Nat = Zero | Succ Nat 

Bằng cách sử dụng phần mở rộng ngôn ngữ DataKinds này loại dữ liệu được đẩy lên cấp loại. Các constuctors dữ liệu Zero,succ được đề bạt lên các nhà thầu loại 'Zero' succ. Với điều này, chúng tôi nhận được cho mỗi số tự nhiên một loại tương ứng duy nhất và duy nhất trên mức loại. Ví dụ: chúng tôi nhận được 'Succ (' Succ '' Succ 'Zero)). Vì vậy, bây giờ chúng ta có số tự nhiên dưới dạng các loại.

Sau đó ông định nghĩa về mức độ giá trị chức năng cộng và vào mức độ kiểu loại gia đình Thêm có các hoạt động bổ sung có sẵn. Với thúc đẩy chức năng/quasiqoter của thư viện độc thân chúng tôi có thể tự động tạo gia đình Thêm loại từ cộng chức năng. Vì vậy, chúng ta có thể tránh viết các loại gia đình ourselfs.

Cho đến nay rất tốt!

Với cú pháp GADT ông cũng định nghĩa một kiểu dữ liệu SNAT:

data SNat :: Nat -> * where 
    SZero :: SNat Zero 
    SSucc :: SNat n -> SNat (Succ n) 

Về cơ bản ông chỉ kết thúc tốt đẹp Nat loại thành một SNAT constructor. Tại sao điều này lại cần thiết? Chúng ta có được gì? Các loại dữ liệu NatSNat không phải là đẳng cấu? Tại sao là SNat một singleton và tại sao là Nat không phải là một singleton? Trong cả hai trường hợp, mọi loại đều có một giá trị duy nhất, số tự nhiên tương ứng.

Trả lời

29

Chúng ta có được gì? Hmm. Tình trạng của người độc thân là của giải pháp khó xử nhưng hiện tại cần thiết và chúng tôi sớm có thể làm việc với họ, càng tốt.

Hãy để tôi xem tôi có thể làm rõ hình ảnh không. Chúng tôi có một kiểu dữ liệu Nat:

data Nat = Zero | Suc Nat 

(chiến tranh đã được bắt đầu trên thậm chí tầm thường nhiều vấn đề hơn số lượng 'của c trong Suc)

Loại Nat có giá trị thời gian chạy mà không thể phân biệt tại loại cấp.Hệ thống kiểu Haskell hiện có thuộc tính thay thế, có nghĩa là trong bất kỳ chương trình được nhập tốt nào, bạn có thể thay thế bất kỳ biểu thức con được gõ tốt bằng cách hiển thị phụ thay thế với cùng phạm vi và loại, và chương trình sẽ tiếp tục được đánh máy tốt. Ví dụ, bạn có thể viết lại mỗi lần xuất hiện của

if <b> then <t> else <e> 

để

if <b> then <e> else <t> 

và bạn có thể chắc chắn rằng không có gì sẽ đi sai ... với kết quả kiểm tra kiểu của chương trình của bạn.

Thuộc tính thay thế là một sự bối rối. Đó là bằng chứng rõ ràng rằng hệ thống kiểu của bạn bỏ cuộc ngay lúc đó ý nghĩa bắt đầu thành vấn đề.

Bây giờ, bằng cách là loại dữ liệu cho giá trị thời gian chạy, Nat cũng trở thành một loại giá trị loại cấp 'Zero'Suc. Sau này chỉ sống trong ngôn ngữ kiểu của Haskell và không có sự hiện diện thời gian chạy nào cả. Xin lưu ý rằng mặc dù 'Zero'Suc tồn tại ở cấp loại, nhưng không hữu ích khi gọi chúng là "loại" và những người hiện đang thực hiện điều đó phải tồn tại. Họ không có loại * và do đó không thể phân loại các giá trị đó là những loại xứng đáng với tên.

Không có phương tiện trao đổi trực tiếp giữa thời gian chạy và loại Nat s, điều này có thể gây phiền toái. Ví dụ mẫu mực liên quan đến một hoạt động quan trọng trên vectơ:

data Vec :: Nat -> * -> * where 
    VNil :: Vec 'Zero x 
    VCons :: x -> Vec n x -> Vec ('Suc n) x 

Chúng tôi có thể muốn tính toán một vector của các bản sao của một nguyên tố (có lẽ như một phần của một thể hiện Applicative). Có thể có một ý tưởng hay là cung cấp loại

vec :: forall (n :: Nat) (x :: *). x -> Vec n x 

nhưng điều đó có thể hoạt động không? Để tạo n bản sao của thứ gì đó, chúng ta cần biết n khi chạy: chương trình phải quyết định có triển khai VNil và dừng hoặc triển khai VCons và tiếp tục và cần một số dữ liệu để thực hiện điều đó. Một đầu mối tốt là số phân định forall, là tham số: nó chỉ ra rằng thông tin định lượng chỉ có sẵn cho các loại và bị xóa theo thời gian chạy.

Haskell hiện thực thi một sự trùng hợp hoàn toàn giả mạo giữa việc định lượng phụ thuộc (những gì forall làm) và xóa bỏ thời gian chạy. Nó không không hỗ trợ một số lượng phụ thuộc nhưng không bị xóa, mà chúng ta thường gọi là pi. Loại và thực hiện vec nên một cái gì đó giống như

vec :: pi (n :: Nat) -> forall (x :: *). Vec n x 
vec 'Zero x = VNil 
vec ('Suc n) x = VCons x (vec n x) 

nơi tranh cãi trong pi -positions được viết bằng ngôn ngữ loại, nhưng các dữ liệu có sẵn tại thời gian chạy.

Vậy chúng ta phải làm gì? Chúng tôi sử dụng các trình đơn để thu thập gián tiếp ý nghĩa của việc sao chép dữ liệu cấp loại .

data SNat :: Nat -> * where 
    SZero :: SNat Zero 
    SSuc :: SNat n -> SNat (Suc n) 

Bây giờ, SZeroSSuc làm cho dữ liệu thời gian chạy.SNat không đồng bộ với Nat: loại cũ có loại Nat -> *, trong khi loại sau có loại *, do đó, đây là lỗi loại để cố gắng làm cho chúng không đồng nhất. Có nhiều giá trị thời gian chạy trong Nat và hệ thống loại không phân biệt chúng; có chính xác một giá trị thời gian chạy (giá trị nói) trong mỗi khác nhau SNat n, do đó, thực tế là hệ thống kiểu không thể phân biệt chúng là bên cạnh điểm. Vấn đề là mỗi SNat n là một loại khác nhau cho mỗi khác nhau n và mẫu phù hợp GADT (trong đó một mẫu có thể là một ví dụ cụ thể hơn của loại GADT được biết là phù hợp) có thể tinh chỉnh kiến ​​thức của chúng tôi về n.

Chúng tôi bây giờ có thể viết

vec :: forall (n :: Nat). SNat n -> forall (x :: *). x -> Vec n x 
vec SZero x = VNil 
vec (SSuc n) x = VCons x (vec n x) 

Singletons cho phép chúng ta thu hẹp khoảng cách giữa thời gian chạy và kiểu dữ liệu cấp, bằng cách khai thác hình thức duy nhất của phân tích thời gian chạy cho phép người tinh tế của loại thông tin. Nó khá hợp lý để tự hỏi liệu họ có thực sự cần thiết không, và hiện tại họ đang có, chỉ vì khoảng cách đó vẫn chưa được loại bỏ.

+0

Không 'LoạiApplications' chủ yếu hoặc thậm chí hoàn toàn làm giảm bớt nhu cầu này? Tôi đã hack một ví dụ nhanh [ở đây] (http://ideone.com/vD4Gxp), mặc dù thừa nhận có lẽ tôi đang thiếu một số hạn chế ở đây. (Bây giờ tôi đang nghĩ về nó mặc dù, tôi không thể thực sự nghĩ ra một cách để làm điều đó mà không có một loại lớp như bạn đã làm ở đây) – Cubic

+1

@pigworker: Rất cám ơn! Bạn viết: "mặc dù" Zero và 'Suc tồn tại ở cấp độ loại, nó là vô ích để gọi họ là' loại 'và những người hiện đang làm điều đó nên desist "Làm thế nào bạn sẽ gọi' Zero và 'Suc? Tôi nghĩ tên tốt luôn hữu ích. – Jogger

+1

@Cubic Lớp loại nào? Và không, TypeApplications liên quan đến * một khác biệt * không liên quan: rõ ràng so với ngầm định. Đó là một sự phân biệt hoàn toàn riêng biệt so với loại so với thời gian chạy. Google "Milinc's Coincidence". – pigworker

Các vấn đề liên quan