MSFP 2020
Artjoms Šinkarovs
01 September 2020
Key features we would like to have in the model:
Static bounds checking and the ability to encode operations such as take
or drop
suggests that arrays should be dependently-typed.
Mat x y = Ar ℕ 2 (x ∷ y ∷ []) transpose : ∀ {a b} → Mat a b → Mat b a transpose (imap a) = imap body where body : _ body (i ∷ j ∷ []) = a $ j ∷ i ∷ []Matrix multiplication:
mm : ∀ {a b c} → Mat a b → Mat b c → Mat a c mm (imap a) (imap b) = imap body where body : _ body (i ∷ j ∷ []) = sum $ imap λ where (k ∷ []) → a (i ∷ k ∷ []) * b (k ∷ j ∷ [])
record Con : Set₁ where constructor _◃_ field Sh : Set Po : Sh → Set ⟦_⟧◃ : Set → Set ⟦_⟧◃ X = Σ Sh λ s → Po s → X
List : Set → Set List X = ⟦ ℕ ◃ Fin ⟧◃ X -- ≡ Σ ℕ λ n → Fin n → X
Ar
is nothing but a container.– Peter Hancock.
Ar₂ : Set → Set Ar₂ X = ⟦ (Σ ℕ λ d → Fin d → ℕ) ◃ (λ where (d , sh) → (i : Fin d) → Fin (sh i)) ⟧◃ XAfter noticing that the first
Σ
is a container:
Ar₃ : Set → Set Ar₃ X = ⟦ ⟦ ℕ ◃ Fin ⟧◃ ℕ ◃ (λ where (d , sh) → (i : Fin d) → Fin (sh i)) ⟧◃ X
Finally, let us generalise this into a container operation:
Π : (A : Set) → (A → Set) → Set Π A B = (i : A) → B i _⋄_ : Con → Con → Con (A ◃ B) ⋄ (C ◃ D) = ⟦ A ◃ B ⟧◃ C ◃ λ { (a , γ) → Π (B a) (D ∘ γ) }
In this case, we can rewrite an array type as:
Array : Set → Set Array X = ⟦ (ℕ ◃ Fin) ⋄ (ℕ ◃ Fin) ⟧◃ X
An intuitive explanation of the _⋄_
can be seen through the tensor product _⊗_
on containers that is defined as:
_⊗_ : Con → Con → Con (A ◃ B) ⊗ (C ◃ D) = (A × C) ◃ λ where (a , c) → B a × D c
Now assume that we want to compute an n-fold tensor product of a container C ◃ D
. That is: (C ◃ D) ⊗ (C ◃ D) ⊗ ⋯
. In this case we can set “the boundaries” of the product using A ◃ B
.
(A ◃ B) ⋄ (C ◃ D) = ⨂(A ◃ B)(C ◃ D)
+
with ×
in:
(A ◃ B) ×' (C ◃ D) = (A × C) ◃ λ where (a , c) → B a ⊎ D c (A ◃ B) ⊗' (C ◃ D) = (A × C) ◃ λ where (a , c) → B a × D c
In a similar way ⋄
replaces Σ
with Π
in:
(A ◃ B) ∘' (C ◃ D) = ⟦ A ◃ B ⟧◃ C ◃ λ where (a , γ) → Σ (B a) (D ∘ γ) (A ◃ B) ⋄' (C ◃ D) = ⟦ A ◃ B ⟧◃ C ◃ λ where (a , γ) → Π (B a) (D ∘ γ)
As _⋄_ : Con → Con → Con
, it can be iterated. As _⋄_
is not associative, iteration on the left and on the right gives different results.
1ₐ : Con 1ₐ = ⊤ ◃ λ _ → ⊤ AL : ℕ → Con AL 0 = 1ₐ AL (suc x) = (ℕ ◃ Fin) ⋄ (AL x) AR : ℕ → Con AR 0 = 1ₐ AR (suc x) = (AR x) ⋄ (ℕ ◃ Fin)
Iteration on the left makes the “counting container” more complex.
AL₃ : Set → Set AL₃ X = ⟦ (ℕ ◃ Fin) ⋄ ((ℕ ◃ Fin) ⋄ (ℕ ◃ Fin)) ⟧◃ X sanityₗ : ∀ X → AL₃ X ≡ Σ (⟦ ℕ ◃ Fin ⟧◃ (⟦ ℕ ◃ Fin ⟧◃ ℕ)) -- Vec of Vec of ℕ λ { (ss , ff) → ((ii : Fin ss) → let s , f = ff ii in (i : Fin s) → Fin (f i)) → X} sanityₗ X = refl -- The shape of level-3 array becomes inhomogeneous, e.g: -- 2 -- 3 4 5 6 -- 1 2
AR₃ : Set → Set AR₃ X = ⟦ ((ℕ ◃ Fin) ⋄ (ℕ ◃ Fin)) ⋄ (ℕ ◃ Fin) ⟧◃ X sanityᵣ : ∀ X → AR₃ X ≡ Σ (⟦ (ℕ ◃ Fin) ⋄ (ℕ ◃ Fin) ⟧◃ ℕ) λ { ((d , s) , ss) -- ss is array of shape s of ℕ → Π (Π (Fin d) (Fin ∘ s)) (Fin ∘ ss) → X} sanityᵣ X = refl
The shape of level-3 array is a level-2 array of ℕ
AR
hierarchyBy adopting AR
, all the shapes (> level-0) are arrays themselves:
Level | Shape | Array |
---|---|---|
level-0 | ⊤ |
“scalars” (0-dimensional) |
level-1 | level-0 of ℕ | vectors (1-dimensional) |
level-2 | level-1 of ℕ | multi-dimensional |
level-3 | level-2 of ℕ | multi-multi-dimensional |
… |
Note: all of the higher-level arrays (> 1) can be mapped into vectors (level 1).
Average pooling example using ranked operator (demo).
ShType : (l : ℕ) → Set IxType : (l : ℕ) → ShType l → Set ReprAr : ∀ l (X : Set) → Set record IxLvl (l : ℕ) (s : ShType l) : Set where constructor ix field flat-ix : IxType l s data ArLvl {a} (l : ℕ) (X : Set a) (s : ShType l) : Set a where imap : (IxLvl l s → X) → ArLvl l X s
prod : ∀ {l} → ShType l → ℕ ShType zero = ⊤ ShType (suc l) = ReprAr l ℕ ReprAr l X = Σ (ShType l) λ s → Vec X (prod {l = l} s) IxType zero tt = ⊤ IxType (suc l) (s , vec) = Ix (prod s) vec prod {zero} sh = 1 prod {suc l} (s , vec) = foldr _ _*_ 1 vec
Big thanks to Peter Hancock and Sven-Bodo Scholz for a number of very productive discussions.