2012-02-27 26 views
9

Tôi có thể thề rằng tôi đã xem một bài viết về điều này gần đây, nhưng tôi không thể tìm thấy nó.Haskell/GHC: Cách viết các biến vị ngữ trên các loại naturals loại

Tôi đang cố gắng để tạo ra một loại để làm mã hóa nhị phân của những con số mod n, nhưng làm như vậy, tôi cần phải có khả năng viết các vị từ trên Naturals mức loại:

{-# LANGUAGE MultiParamTypeClasses #-} 
{-# LANGUAGE TypeFamilies #-} 
{-# LANGUAGE GADTs #-} 
{-# LANGUAGE TypeSynonymInstances #-} 
{-# LANGUAGE FunctionalDependencies #-} 
{-# LANGUAGE FlexibleInstances #-} 
{-# LANGUAGE FlexibleContexts #-} 
{-# LANGUAGE UndecidableInstances #-} 
module Modulo where 

-- (pseudo-)binary representation of 
-- numbers mod n 
-- 
-- e.g. Mod Seven contains 
-- Zero . Zero . Zero $ Stop 
-- Zero . Zero . One $ Stop 
-- Zero . One . Zero $ Stop 
-- Zero . One . One $ Stop 
-- One . Zero . Zero $ Stop 
-- One . Zero . One $ Stop 
-- One . One $ Stop 
data Mod n where 
    Stop :: Mod One 
    Zero :: Split n => Mod (Lo n) -> Mod n 
    One :: Split n => Mod (Hi n) -> Mod n 

-- type-level naturals 
data One 
data Succ n 
type Two = Succ One 

-- predicate to allow us to compare 
-- natural numbers 
class Compare n n' b | n n' -> b 

-- type-level ordering 
data LT 
data EQ 
data GT 

-- base cases 
instance Compare One One EQ 
instance Compare One (Succ n) LT 
instance Compare (Succ n) One GT 

-- recurse 
instance Compare n n' b => Compare (Succ n) (Succ n') b 

-- predicate to allow us to break down 
-- natural numbers by their bit structure 
-- 
-- if every number mod n can be represented in m bits, then 
class Split n where 
    type Lo n -- number of values mod n where the m'th bit is 0 
    type Hi n -- number of values mod n where the m'th bit is 1 

-- base case, n = 2 
-- for this, we only need m=1 bit 
instance Split Two where 
    type Lo Two = One -- 0 mod 2 
    type Hi Two = One -- 1 mod 2 

-- recurse 
-- if (Lo n) == (Hi n), then n = 2^m, so 
-- the values mod (n+1) will require one extra bit 
instance (Split n, Compare (Lo n) (Hi n) EQ) => Split (Succ n) where 
    type Lo (Succ n) = n -- all the numbers mod n will be prefixed by a 0 
    type Hi (Succ n) = One -- and one extra, which will be 10...0 

-- recurse 
-- if (Lo n) > (Hi n), then n < 2^m, so 
-- the values mod (n+1) still only require m bits 
instance (Split n, Compare (Lo n) (Hi n) GT) => Split (Succ n) where 
    type Lo (Succ n) = Lo (n)  -- we've got the same number of lower values 
    type Hi (Succ n) = Succ (Hi n) -- and one more higher value 

My hiện tại triển khai thực hiện trích xuất một loạt lỗi trình biên dịch:

Nat.hs:60:8: 
    Conflicting family instance declarations: 
     type Lo Two -- Defined at Nat.hs:60:8-9 
     type Lo (Succ n) -- Defined at Nat.hs:74:8-9 

Nat.hs:61:8: 
    Conflicting family instance declarations: 
     type Hi Two -- Defined at Nat.hs:61:8-9 
     type Hi (Succ n) -- Defined at Nat.hs:75:8-9 

Nat.hs:66:10: 
    Duplicate instance declarations: 
     instance (Split n, Compare (Lo n) (Hi n) EQ) => Split (Succ n) 
     -- Defined at Nat.hs:66:10-62 
     instance (Split n, Compare (Lo n) (Hi n) GT) => Split (Succ n) 
     -- Defined at Nat.hs:73:10-62 

Nat.hs:67:8: 
    Conflicting family instance declarations: 
     type Lo (Succ n) -- Defined at Nat.hs:67:8-9 
     type Lo (Succ n) -- Defined at Nat.hs:74:8-9 

Nat.hs:68:8: 
    Conflicting family instance declarations: 
     type Hi (Succ n) -- Defined at Nat.hs:68:8-9 
     type Hi (Succ n) -- Defined at Nat.hs:75:8-9 

Điều này khiến tôi nghĩ rằng tôi sẽ viết sai vị ngữ của tôi nếu chúng cho rằng chúng xung đột.

Tôi làm cách nào để làm đúng?

+0

Bằng "naturals", ý bạn là "số tự nhiên"? – Charles

+1

Loại gia đình và các trường hợp trùng lặp không hoạt động cùng nhau ngay bây giờ. Xem qua tại http://hackage.haskell.org/trac/ghc/ticket/4259 –

Trả lời

14

Vấn đề xung đột đủ đơn giản. Các rules for overlapping type families khá đơn giản:

các tờ khai thể hiện của một gia đình loại được sử dụng trong một chương trình duy nhất chỉ có thể chồng lên nhau nếu hai bên cánh tay phải của trường hợp chồng chéo trùng cho các loại chồng chéo. Chính thức hơn, hai khai báo cá thể chồng lên nhau nếu có một sự thay thế làm cho các cạnh bên trái của các cá thể có cú pháp giống nhau. Bất cứ khi nào đó là trường hợp, các bên tay phải của các cá thể cũng phải được cú pháp bằng nhau trong cùng một sự thay thế.

Lưu ý rằng nó chỉ định cú pháp bằng nhau. Hãy xem xét hai trường hợp sau đây:

instance Split Two where 
    type Lo Two = One -- 0 mod 2 
    type Hi Two = One -- 1 mod 2 

instance Split (Succ n) where 
    type Lo (Succ n) = Lo (n) 
    type Hi (Succ n) = Succ (Hi n) 

Two được định nghĩa là Succ One, và từ đồng nghĩa kiểu đồng bằng được mở rộng cho các mục đích bình đẳng cú pháp, vì vậy đây là những bằng ở hai bên tay trái; nhưng bên tay phải thì không, do đó lỗi.

Bạn có thể nhận thấy rằng tôi đã xóa ngữ cảnh lớp khỏi mã ở trên. Vấn đề sâu hơn và có lẽ lý do tại sao bạn không mong đợi xung đột trên xảy ra, là các trường hợp trùng lặp thực sự các bản sao trùng lặp. Các ngữ cảnh lớp, như mọi khi, bỏ qua cho các mục đích chọn lựa, và nếu bộ nhớ phục vụ tôi điều này tăng gấp đôi cho các họ kiểu kết hợp, phần lớn là một sự thuận tiện cú pháp cho các họ kiểu không liên kết và có thể không bị ràng buộc bởi lớp theo cách bạn muốn chờ đợi.

Bây giờ, rõ ràng hai trường hợp nên thể riêng biệt, và bạn muốn chọn giữa chúng dựa trên kết quả của việc sử dụng Compare, do đó kết quả mà phải là một tham số của lớp loại, không chỉ đơn thuần là một hạn chế. Bạn cũng đang trộn các gia đình kiểu với các phụ thuộc chức năng ở đây, điều này không cần thiết khó xử. Vì vậy, bắt đầu từ đầu, chúng tôi sẽ giữ các loại cơ bản:

-- type-level naturals 
data One 
data Succ n 
type Two = Succ One 

-- type-level ordering 
data LT 
data EQ 
data GT 

Viết lại Compare chức năng như một loại gia đình:

type family Compare n m :: * 
type instance Compare One One = EQ 
type instance Compare (Succ n) One = GT 
type instance Compare One (Succ n) = LT 
type instance Compare (Succ n) (Succ m) = Compare n m 

Bây giờ, để xử lý các điều kiện chúng tôi sẽ cần một số loại của nhóm "kiểm soát luồng" gia đình. Tôi sẽ định nghĩa một cái gì đó chung chung hơn một chút ở đây để cải tiến và cảm hứng; chuyên theo khẩu vị.

Chúng tôi sẽ cung cấp cho nó một vị ngữ, một đầu vào, và hai chi nhánh để chọn từ:

type family Check pred a yes no :: * 

Chúng tôi sẽ cần một vị để thử nghiệm Compare 's kết quả:

data CompareAs a 
type instance (CompareAs LT) LT yes no = yes 
type instance (CompareAs EQ) LT yes no = no 
type instance (CompareAs GT) LT yes no = no 
type instance (CompareAs LT) EQ yes no = no 
type instance (CompareAs EQ) EQ yes no = yes 
type instance (CompareAs GT) EQ yes no = no 
type instance (CompareAs LT) GT yes no = no 
type instance (CompareAs EQ) GT yes no = no 
type instance (CompareAs GT) GT yes no = yes 

Đó là một tập hợp các định nghĩa tẻ nhạt khủng khiếp, được cấp và tiên lượng là khá khủng khiếp khi so sánh các tập hợp loại giá trị lớn hơn. Có nhiều cách tiếp cận mở rộng hơn (một loại thẻ giả và một sự đánh dấu với các naturals là một giải pháp rõ ràng và hiệu quả) nhưng đó là một chút vượt quá phạm vi của câu trả lời này. Ý tôi là, chúng tôi chỉ đang thực hiện tính toán ở mức loại ở đây, chúng ta sẽ không nhận được vô lý hoặc bất kỳ thứ gì.

đơn giản trong trường hợp này sẽ được chỉ cần xác định một chức năng phân tích trường hợp trên kết quả so sánh:

type family CaseCompare cmp lt eq gt :: * 
type instance CaseCompare LT lt eq gt = lt 
type instance CaseCompare EQ lt eq gt = eq 
type instance CaseCompare GT lt eq gt = gt 

tôi sẽ sử dụng sau này cho bây giờ, nhưng nếu bạn muốn điều kiện phức tạp hơn nơi khác một cách tiếp cận chung bắt đầu có ý nghĩa hơn.

Dù sao đi nữa. Chúng ta có thể chia ... er, Split lớp thành gia đình kiểu hủy liên kết giữa:

data Oops 

type family Lo n :: * 
type instance Lo Two = One 
type instance Lo (Succ (Succ n)) 
    = CaseCompare (Compare (Lo (Succ n)) (Hi (Succ n))) 
        Oops -- yay, type level inexhaustive patterns 
        (Succ n) 
        (Lo (Succ n)) 

type family Hi n :: * 
type instance Hi Two = One 
type instance Hi (Succ (Succ n)) 
    = CaseCompare (Compare (Lo (Succ n)) (Hi (Succ n))) 
        Oops -- yay, type level inexhaustive patterns 
        One 
        (Succ (Hi (Succ n))) 

Điểm quan trọng nhất ở đây là (có vẻ như không cần thiết) sử dụng (Succ (Succ n)) --Thư lý do cho điều này là hai Succ nhà xây dựng là cần thiết để phân biệt đối số từ Two, do đó tránh được các lỗi xung đột bạn đã thấy.

Lưu ý rằng tôi đã di chuyển mọi thứ để nhập các gia đình vào đây để đơn giản vì nó hoàn toàn là loại cấp. Nếu bạn cũng muốn xử lý các giá trị khác nhau tùy thuộc vào các tính toán trên - bao gồm gián tiếp, thông qua loại Mod - bạn có thể cần phải thêm các định nghĩa lớp thích hợp trở lại, vì chúng được yêu cầu để chọn các cụm từ dựa trên loại, thay vì chỉ chọn loại dựa trên các loại.

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