Vì vậy, tôi đã nghĩ về nó nhiều hơn một chút và đạt được một số tiến bộ. Đây là bước đầu tiên trong việc mã hóa Set : Set
hệ thống đơn giản (nhưng không nhất quán) của Martin-Löf theo phong cách tổ hợp. Đây không phải là cách tốt để kết thúc, nhưng đây là cách dễ dàng nhất để bắt đầu. Cú pháp của lý thuyết kiểu này chỉ là phép tính lambda với chú thích kiểu, kiểu Pi và một Tập hợp vũ trụ.
Lý thuyết loại mục tiêu
Vì lợi ích của sự hoàn chỉnh, tôi sẽ trình bày các quy tắc. Tính hợp lệ của ngữ cảnh chỉ nói rằng bạn có thể xây dựng các ngữ cảnh từ trống bằng cách kết nối các biến mới đang cư trú Set
.
G |- valid G |- S : Set
. |- valid G, x:S |- valid
Và bây giờ chúng ta có thể nói cách tổng hợp các loại cho các thuật ngữ trong bất kỳ ngữ cảnh nhất định nào và cách thay đổi loại của một thứ gì đó theo hành vi tính toán của các thuật ngữ mà nó chứa.
G |- valid G |- S : Set G |- T : Pi S \ x:S -> Set
G |- Set : Set G |- Pi S T : Set
G |- S : Set G, x:S |- t : T x G |- f : Pi S T G |- s : S
G |- \ x:S -> t : Pi S T G |- f s : T s
G |- valid G |- s : S G |- T : Set
G |- x : S G |- s : T
Trong một biến thể nhỏ so với bản gốc, tôi đã đặt lambda trở thành toán tử ràng buộc duy nhất, vì vậy đối số thứ hai của Pi phải là một hàm tính toán theo cách kiểu trả về phụ thuộc vào đầu vào. Theo quy ước (ví dụ: trong Agda, nhưng đáng buồn là không phải trong Haskell), phạm vi của lambda mở rộng sang bên phải càng nhiều càng tốt, vì vậy bạn thường có thể để các phần trừu tượng không được đánh dấu khi chúng là đối số cuối cùng của toán tử bậc cao hơn: bạn có thể thấy tôi đã làm điều đó với Pi. Loại Agda của bạn (x : S) -> T
trở thành Pi S \ x:S -> T
.
(Ký hiệu hóa . Nhập chú thích trên lambda là cần thiết nếu bạn muốn có thể tổng hợp loại trừu tượng. Nếu bạn chuyển sang kiểm tra loại dưới dạng toán hạng mô đun của mình, bạn vẫn cần chú thích để kiểm tra một phiên bản beta-redx (\ x -> t) s
, vì bạn không có cách nào để đoán các loại của các bộ phận từ tổng thể. Tôi khuyên các nhà thiết kế hiện đại nên kiểm tra các loại và loại trừ các lỗi beta khỏi chính cú pháp.)
( Digression . Hệ thống này là không phù hợp như Set:Set
cho phép mã hóa của một loạt các "nghịch lý kẻ nói dối". Khi Martin-LOF đề xuất lý thuyết này, Girard gửi cho ông một mã hóa của nó trong mâu thuẫn của chính mình hệ thống U. Nghịch lý tiếp theo do Hurkens là xây dựng độc hại gọn gàng nhất mà chúng tôi biết.)
Cú pháp tổ hợp và chuẩn hóa
Nhưng dù sao, chúng tôi có hai ký hiệu phụ, Pi và Set, vì vậy chúng tôi có thể quản lý một bản dịch tổ hợp với S, K và hai ký hiệu phụ: Tôi chọn U cho vũ trụ và P cho sản phẩm.
Bây giờ chúng ta có thể xác định cú pháp tổ hợp không định kiểu (với các biến miễn phí):
data SKUP = S | K | U | P deriving (Show, Eq)
data Unty a
= C SKUP
| Unty a :. Unty a
| V a
deriving (Functor, Eq)
infixl 4 :.
Lưu ý rằng tôi đã bao gồm các phương tiện để bao gồm các biến miễn phí được đại diện bởi kiểu a
trong cú pháp này. Ngoài việc là một phản xạ từ phía tôi (mọi cú pháp xứng đáng với tên gọi là một đơn nguyên miễn phí với return
các biến nhúng và >>=
thay thế hoàn thiện), sẽ rất hữu ích khi đại diện cho các giai đoạn trung gian trong quá trình chuyển đổi các thuật ngữ có ràng buộc với dạng tổ hợp của chúng.
Đây là chuẩn hóa:
norm :: Unty a -> Unty a
norm (f :. a) = norm f $. a
norm c = c
($.) :: Unty a -> Unty a -> Unty a
C S :. f :. a $. g = f $. g $. (a :. g)
C K :. a $. g = a
n $. g = n :. norm g
infixl 4 $.
(Một bài tập cho người đọc là xác định một kiểu cho chính xác các dạng thông thường và làm sắc nét các kiểu của các phép toán này.)
Lý thuyết kiểu biểu diễn
Bây giờ chúng ta có thể xác định một cú pháp cho lý thuyết kiểu của chúng ta.
data Tm a
= Var a
| Lam (Tm a) (Tm (Su a))
| Tm a :$ Tm a
| Pi (Tm a) (Tm a)
| Set
deriving (Show, Functor)
infixl 4 :$
data Ze
magic :: Ze -> a
magic x = x `seq` error "Tragic!"
data Su a = Ze | Su a deriving (Show, Functor, Eq)
Tôi sử dụng biểu diễn chỉ số de Bruijn theo cách Bellegarde và Hook (được phổ biến bởi Bird và Paterson). Kiểu Su a
có nhiều phần tử hơn a
, và chúng tôi sử dụng nó như kiểu của các biến tự do trong một chất kết dính, với Ze
làm biến mới được ràng buộc và Su x
là đại diện dịch chuyển của biến tự do cũ x
.
Dịch thuật ngữ sang người kết hợp
Và sau khi hoàn thành, chúng tôi có được bản dịch thông thường, dựa trên sự trừu tượng hóa dấu ngoặc .
tm :: Tm a -> Unty a
tm (Var a) = V a
tm (Lam _ b) = bra (tm b)
tm (f :$ a) = tm f :. tm a
tm (Pi a b) = C P :. tm a :. tm b
tm Set = C U
bra :: Unty (Su a) -> Unty a
bra (V Ze) = C S :. C K :. C K
bra (V (Su x)) = C K :. V x
bra (C c) = C K :. C c
bra (f :. a) = C S :. bra f :. bra a
Gõ Combinators
Bản dịch cho thấy cách chúng ta sử dụng các tổ hợp, cung cấp cho chúng ta manh mối về loại của chúng nên là gì. U
và P
chỉ là các hàm tạo được thiết lập, vì vậy, khi viết các kiểu chưa được dịch và cho phép "ký hiệu Agda" cho Pi, chúng ta nên có
U : Set
P : (A : Set) -> (B : (a : A) -> Set) -> Set
Bộ K
tổ hợp được sử dụng để nâng một giá trị của một số loại A
lên một hàm không đổi so với một số loại khác G
.
G : Set A : Set
K : (a : A) -> (g : G) -> A
Bộ S
kết hợp được sử dụng để nâng các ứng dụng trên một loại, mà tất cả các bộ phận có thể phụ thuộc vào.
G : Set
A : (g : G) -> Set
B : (g : G) -> (a : A g) -> Set
S : (f : (g : G) -> (a : A g) -> B g a ) ->
(a : (g : G) -> A g ) ->
(g : G) -> B g (a g)
Nếu bạn nhìn vào kiểu S
, bạn sẽ thấy rằng nó nêu chính xác quy tắc ứng dụng theo ngữ cảnh của lý thuyết kiểu, vì vậy đó là điều làm cho nó phù hợp để phản ánh cấu trúc ứng dụng. Đó là công việc của nó!
Sau đó, chúng tôi chỉ có ứng dụng cho những thứ đã đóng cửa
f : Pi A B
a : A
f a : B a
Nhưng có một khó khăn. Tôi đã viết các loại tổ hợp trong lý thuyết kiểu thông thường, không phải lý thuyết kiểu tổ hợp. May mắn thay, tôi có một chiếc máy sẽ thực hiện bản dịch.
Hệ thống loại tổ hợp
U : U
P : PU(S(S(KP)(S(S(KP)(SKK))(S(KK)(KU))))(S(KK)(KU)))
G : U
A : U
K : P[A](S(S(KP)(K[G]))(S(KK)(K[A])))
G : U
A : P[G](KU)
B : P[G](S(S(KP)(S(K[A])(SKK)))(S(KK)(KU)))
S : P(P[G](S(S(KP)(S(K[A])(SKK)))(S(S(KS)(S(S(KS)(S(KK)(K[B])))(S(KK)(SKK))))
(S(S(KS)(KK))(KK)))))(S(S(KP)(S(S(KP)(K[G]))(S(S(KS)(S(KK)(K[A])))
(S(S(KS)(KK))(KK)))))(S(S(KS)(S(S(KS)(S(KK)(KP)))(S(KK)(K[G]))))
(S(S(KS)(S(S(KS)(S(KK)(KS)))(S(S(KS)(S(S(KS)(S(KK)(KS)))
(S(S(KS)(S(KK)(KK)))(S(KK)(K[B])))))(S(S(KS)(S(S(KS)(S(KK)(KS)))(S(KK)(KK))))
(S(KK)(KK))))))(S(S(KS)(S(S(KS)(S(KK)(KS)))(S(S(KS)(S(KK)(KK)))
(S(S(KS)(KK))(KK)))))(S(S(KS)(S(S(KS)(S(KK)(KS)))(S(KK)(KK))))(S(KK)(KK)))))))
M : A B : U
M : B
Vì vậy, bạn có nó, trong tất cả vinh quang không thể đọc được của nó: một bài thuyết trình kết hợp của Set:Set
!
Vẫn còn một chút vấn đề. Cú pháp của hệ thống không cung cấp cho bạn cách nào để đoán G
, A
và B
các tham số cho S
và tương tự K
, chỉ từ các thuật ngữ. Tương ứng, chúng tôi có thể xác minh các dẫn xuất nhập bằng thuật toán, nhưng chúng tôi không thể chỉ đánh máy các cụm từ tổ hợp như chúng tôi có thể làm với hệ thống ban đầu. Những gì có thể hoạt động là yêu cầu đầu vào cho người đánh máy để ghi chú thích kiểu về việc sử dụng S và K, ghi lại hiệu quả dẫn xuất. Nhưng đó là một lon giun khác ...
Đây là một nơi tốt để dừng lại, nếu bạn đã đủ quan tâm để bắt đầu. Phần còn lại là những thứ "hậu trường".
Tạo các loại tổ hợp
Tôi đã tạo các kiểu tổ hợp đó bằng cách sử dụng bản dịch trừu tượng trong ngoặc từ các thuật ngữ lý thuyết kiểu có liên quan. Để cho thấy cách tôi đã làm và làm cho bài đăng này không hoàn toàn vô nghĩa, hãy để tôi cung cấp thiết bị của mình.
Tôi có thể viết các loại tổ hợp, được tóm tắt đầy đủ về các tham số của chúng, như sau. Tôi sử dụng pil
hàm tiện dụng của mình , kết hợp Pi và lambda để tránh lặp lại kiểu miền và thay vào đó, cho phép tôi sử dụng không gian hàm của Haskell để liên kết các biến một cách hữu ích. Có lẽ bạn gần như có thể đọc những điều sau đây!
pTy :: Tm a
pTy = fmap magic $
pil Set $ \ _A -> pil (pil _A $ \ _ -> Set) $ \ _B -> Set
kTy :: Tm a
kTy = fmap magic $
pil Set $ \ _G -> pil Set $ \ _A -> pil _A $ \ a -> pil _G $ \ g -> _A
sTy :: Tm a
sTy = fmap magic $
pil Set $ \ _G ->
pil (pil _G $ \ g -> Set) $ \ _A ->
pil (pil _G $ \ g -> pil (_A :$ g) $ \ _ -> Set) $ \ _B ->
pil (pil _G $ \ g -> pil (_A :$ g) $ \ a -> _B :$ g :$ a) $ \ f ->
pil (pil _G $ \ g -> _A :$ g) $ \ a ->
pil _G $ \ g -> _B :$ g :$ (a :$ g)
Với những điều này được xác định, tôi trích xuất các điều khoản con mở có liên quan và chạy chúng qua bản dịch.
Bộ công cụ mã hóa A de Bruijn
Đây là cách xây dựng pil
. Đầu tiên, tôi định nghĩa một lớp các Fin
tập lặp, được sử dụng cho các biến. Mỗi tập hợp như vậy đều có phần emb
viền bảo toàn hàm tạo vào tập hợp ở trên, cộng với một top
phần tử mới và bạn có thể phân biệt chúng: embd
hàm cho bạn biết liệu một giá trị có trong hình ảnh của emb
.
class Fin x where
top :: Su x
emb :: x -> Su x
embd :: Su x -> Maybe x
Chúng tôi có thể, tất nhiên, thuyết minh Fin
cho Ze
vàSuc
instance Fin Ze where
top = Ze
emb = magic
embd _ = Nothing
instance Fin x => Fin (Su x) where
top = Su top
emb Ze = Ze
emb (Su x) = Su (emb x)
embd Ze = Just Ze
embd (Su x) = fmap Su (embd x)
Bây giờ tôi có thể xác định ít hơn hoặc bằng, với một phép toán yếu dần .
class (Fin x, Fin y) => Le x y where
wk :: x -> y
Các wk
chức năng nên nhúng các yếu tố của x
là lớn nhất các yếu tố của y
, do đó những điều bổ sung trong y
nhỏ hơn, và do đó trong de về chỉ số Bruijn, ràng buộc địa phương hơn.
instance Fin y => Le Ze y where
wk = magic
instance Le x y => Le (Su x) (Su y) where
wk x = case embd x of
Nothing -> top
Just y -> emb (wk y)
Và một khi bạn đã sắp xếp xong điều đó, thì phần còn lại sẽ thực hiện phần còn lại.
lam :: forall x. Tm x -> ((forall y. Le (Su x) y => Tm y) -> Tm (Su x)) -> Tm x
lam s f = Lam s (f (Var (wk (Ze :: Su x))))
pil :: forall x. Tm x -> ((forall y . Le (Su x) y => Tm y) -> Tm (Su x)) -> Tm x
pil s f = Pi s (lam s f)
Hàm bậc cao hơn không chỉ cung cấp cho bạn một thuật ngữ đại diện cho biến, nó còn cung cấp cho bạn một thứ được nạp chồng trở thành đại diện chính xác của biến trong bất kỳ phạm vi nào mà biến có thể nhìn thấy. Đó là, thực tế là tôi gặp khó khăn trong việc phân biệt các phạm vi khác nhau theo loại cung cấp cho người đánh máy Haskell đủ thông tin để tính toán chuyển dịch cần thiết cho bản dịch sang biểu diễn de Bruijn. Tại sao lại nuôi chó và tự sủa?