[英]Why do we need containers?
(作為借口:標題模仿“ 為什么我們需要單子?”的標題。 )
有容器 (和帶索引的容器 )(和具有哈希的容器 )和說明 。 但是容器是有問題的 ,以我很小的經驗,就容器而言,要比對描述更難思考。 非索引容器的類型與Σ
同構-太不確定了。 形狀和位置描述有幫助,但在
⟦_⟧ᶜ : ∀ {α β γ} -> Container α β -> Set γ -> Set (α ⊔ β ⊔ γ)
⟦ Sh ◃ Pos ⟧ᶜ A = ∃ λ sh -> Pos sh -> A
Kᶜ : ∀ {α β} -> Set α -> Container α β
Kᶜ A = A ◃ const (Lift ⊥)
我們本質上是使用Σ
而不是形狀和位置。
容器上嚴格正值的免費monad的類型有一個非常簡單的定義,但是Freer
monad的類型對我來說看起來更簡單(從某種意義上講, Freer
monads甚至比本文中介紹的常規Free
monad更好)。
那么,與描述或其他方式相比,我們能以更好的方式處理容器嗎?
在我看來,容器的價值(就像在容器理論中一樣)是它們的統一性 。 這種統一性為使用容器表示形式作為可執行規范甚至是機器輔助程序派生的基礎提供了很大的范圍。
我不建議將(規范化)容器的固定點作為實現遞歸數據結構的良好通用方法。 也就是說,知道給定的函子具有(最多等值的)容器表示形式是有幫助的,因為它告訴您可以實例化通用容器功能(由於一致性,很容易實現,一次就可以實現)。給您的特定函子,以及您應該期望的行為。 但這並不是說容器實現以任何實際方式都是有效的。 確實,我通常更喜歡一階數據的一階編碼(標簽和元組,而不是函數)。
要修復術語,可以說容器的Cont
類型(在Set
,但其他類別可用)由構造函數<|
給出<|
包裝兩個字段,形狀和位置
S : Set
P : S -> Set
(這與用於確定Sigma類型,Pi類型或W類型的數據具有相同的簽名,但這並不意味着容器與這些事物相同,或者這些事物相同彼此。)
像這樣的函子的解釋是
[_]C : Cont -> Set -> Set
[ S <| P ]C X = Sg S \ s -> P s -> X -- I'd prefer (s : S) * (P s -> X)
mapC : (C : Cont){X Y : Set} -> (X -> Y) -> [ C ]C X -> [ C ]C Y
mapC (S <| P) f (s , k) = (s , f o k) -- o is composition
而且我們已經贏了。 這是一次全部實施的map
。 此外,函子定律僅通過計算即可滿足。 無需遞歸地構造類型或構造規則或證明定律。
從運作上,沒有人試圖聲稱Nat <| Fin
Nat <| Fin
提供了列表的有效實現,只是通過進行標識,我們了解了一些有關列表結構的有用信息。
讓我說一些關於描述的事 。 為了讓懶惰的讀者受益,讓我重新構造它們。
data Desc : Set1 where
var : Desc
sg pi : (A : Set)(D : A -> Desc) -> Desc
one : Desc -- could be Pi with A = Zero
_*_ : Desc -> Desc -> Desc -- could be Pi with A = Bool
con : Set -> Desc -- constant descriptions as singleton tuples
con A = sg A \ _ -> one
_+_ : Desc -> Desc -> Desc -- disjoint sums by pairing with a tag
S + T = sg Two \ { true -> S ; false -> T }
Desc
中的值描述其固定點給出數據類型的函子。 他們描述了哪些函子?
[_]D : Desc -> Set -> Set
[ var ]D X = X
[ sg A D ]D X = Sg A \ a -> [ D a ]D X
[ pi A D ]D X = (a : A) -> [ D a ]D X
[ one ]D X = One
[ D * D' ]D X = Sg ([ D ]D X) \ _ -> [ D' ]D X
mapD : (D : Desc){X Y : Set} -> (X -> Y) -> [ D ]D X -> [ D ]D Y
mapD var f x = f x
mapD (sg A D) f (a , d) = (a , mapD (D a) f d)
mapD (pi A D) f g = \ a -> mapD (D a) f (g a)
mapD one f <> = <>
mapD (D * D') f (d , d') = (mapD D f d , mapD D' f d')
我們不可避免地不得不通過遞歸來進行描述,因此這更加困難。 函子法也並非免費提供。 從操作上來說,我們可以更好地表示數據,因為當具體的元組可用時,我們不必求助於功能編碼。 但是我們必須更加努力地編寫程序。
請注意,每個容器都有一個描述:
sg S \ s -> pi (P s) \ _ -> var
但是,每個描述都有一個同構容器表示形式也是正確的。
ShD : Desc -> Set
ShD D = [ D ]D One
PosD : (D : Desc) -> ShD D -> Set
PosD var <> = One
PosD (sg A D) (a , d) = PosD (D a) d
PosD (pi A D) f = Sg A \ a -> PosD (D a) (f a)
PosD one <> = Zero
PosD (D * D') (d , d') = PosD D d + PosD D' d'
ContD : Desc -> Cont
ContD D = ShD D <| PosD D
也就是說,容器是描述的一種正常形式。 此練習表明[ D ]DX
與[ ContD D ]CX
自然同構。 這使工作變得更輕松,因為說出要做什么,從原則上說出要對它們的正常形式(容器)做什么就足夠了。 原則上,可以通過將同構融合到mapC
的統一定義中來獲得上述mapD
操作。
同樣,如果我們有相等的概念,我們可以說容器統一的單孔上下文
_-[_] : (X : Set) -> X -> Set
X -[ x ] = Sg X \ x' -> (x == x') -> Zero
dC : Cont -> Cont
dC (S <| P) = (Sg S P) <| (\ { (s , p) -> P s -[ p ] })
即,容器中的單孔上下文的形狀是原始容器的形狀與孔的位置的對。 這些位置是孔的原始位置。 這是區分冪級數時“乘以索引,減少索引”的與證明相關的版本。
這種統一的處理方式為我們提供了規范,從中我們可以得出具有數百年歷史的程序來計算多項式的導數。
dD : Desc -> Desc
dD var = one
dD (sg A D) = sg A \ a -> dD (D a)
dD (pi A D) = sg A \ a -> (pi (A -[ a ]) \ { (a' , _) -> D a' }) * dD (D a)
dD one = con Zero
dD (D * D') = (dD D * D') + (D * dD D')
如何檢查我的派生運算符的描述是否正確? 通過對照容器的派生檢查它!
不要陷入這樣的陷阱,即僅僅因為提出某個想法對操作沒有幫助,就不能在概念上有所幫助。
最后一件事。 Freer
技巧等於以特定方式重新排列任意函子(切換到Haskell)
data Obfuncscate f x where
(:<) :: forall p. f p -> (p -> x) -> Obfuncscate f x
但這不是容器的替代方案 。 這只是容器介紹的一個小問題。 如果我們有很強的存在性和依賴類型,我們可以寫
data Obfuncscate f x where
(:<) :: pi (s :: exists p. f p) -> (fst s -> x) -> Obfuncscate f x
這樣(exists p. fp)
表示形狀(您可以在其中選擇位置表示,然后在每個位置標記其位置),然后fst
從形狀(您選擇的位置表示)中選擇存在的見證人。 它具有明顯地嚴格肯定的優點, 確切地說是因為它是容器表示形式。
當然,在Haskell中,您必須處理存在的問題,所幸的是,存在的問題僅依賴於類型投影。 存在性的弱點證明了Obfuncscate f
和f
的等價性是正確的。 如果您在具有強存在性的從屬類型理論中嘗試相同的技巧,則編碼會失去其唯一性,因為您可以投影並區分不同的位置表示選擇。 也就是說,我可以代表Just 3
Just () :< const 3
或
Just True :< \ b -> if b then 3 else 5
在Coq中,可以證明它們是截然不同的。
容器類型之間的每個多態函數都以特定方式給出。 這種統一正在努力澄清我們的理解。
如果你有一些
f : {X : Set} -> [ S <| T ]C X -> [ S' <| T' ]C X
它是由(以下)數據(擴展地)給出的,該數據完全不涉及元素:
toS : S -> S'
fromP : (s : S) -> P' (toS s) -> P s
f (s , k) = (toS s , k o fromP s)
也就是說,在容器之間定義多態函數的唯一方法是說如何將輸入形狀轉換為輸出形狀,然后說如何從輸入位置填充輸出位置。
對於首選的嚴格正函子的表示,請對多態函數進行相似的嚴格刻畫,以消除元素類型的抽象。 (為便於說明,我將完全使用它們對容器的可簡化性。)
給定兩個函子f
和g
,很容易說出它們的組成fog
是什么: (fog) x
將事物包裹在f (gx)
,從而得到“ g
結構的f
結構”。 但是,您是否可以輕易施加附加條件,即存儲在f
結構中的所有g
結構都具有相同的形狀?
假設f >< g
捕獲fog
的可轉置片段,其中所有g
形狀都相同,因此我們也可以將事物轉換為f
結構的g
結構。 例如,雖然[] o []
給出了列表的參差不齊的列表,但是[] >< []
給出了矩形矩陣; [] >< Maybe
會列出全部為Nothing
或全部Just
。
給出><
作為您首選的嚴格表示為正的函子。 對於容器,這很容易。
(S <| P) >< (S' <| P') = (S * S') <| \ { (s , s') -> P s * P' s' }
規范化的Sigma-then-Pi格式的容器並不旨在成為數據的有效機器表示。 但是,對於給定的函子,要實現的知識卻可以作為容器展示,這有助於我們理解其結構並提供有用的設備。 當必須針對其他演示文稿逐案給出容器時,可以一次為容器抽象地給出許多有用的結構。 因此,是的,了解容器是一個好主意,只要掌握您實際實現的更具體的構造背后的原理即可。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.