仅使用 foldr 定义 zip 时出现无限类型错误;可以修复吗?
Infinite type error when defining zip with foldr only; can it be fixed?
(有关此内容的上下文,请参阅 )。
我试图仅使用 foldr
得出 zip
的定义:
zipp :: [a] -> [b] -> [(a,b)]
zipp xs ys = zip1 xs (zip2 ys)
where
-- zip1 :: [a] -> tq -> [(a,b)] -- zip1 xs :: tr ~ tq -> [(a,b)]
zip1 xs q = foldr (\ x r q -> q x r ) n xs q
-------- c --------
n q = []
-- zip2 :: [b] -> a -> tr -> [(a,b)] -- zip2 ys :: tq ~ a -> tr -> [(a,b)]
zip2 ys x r = foldr (\ y q x r -> (x,y) : r q ) m ys x r
---------- k --------------
m x r = []
{-
zipp [x1,x2,x3] [y1,y2,y3,y4]
= c x1 (c x2 (c xn n)) (k y1 (k y2 (k y3 (k y4 m))))
--------------- ----------------------
r q
= k y1 (k y2 (k y3 (k y4 m))) x1 (c x2 (c xn n))
---------------------- ---------------
q r
-}
它在纸面上 "works",但给出了两个 "infinite type" 错误:
Occurs check: cannot construct the infinite type:
t1 ~ (a -> t1 -> [(a, b)]) -> [(a, b)] -- tr
Occurs check: cannot construct the infinite type:
t0 ~ a -> (t0 -> [(a, b)]) -> [(a, b)] -- tq
显然,每种类型 tr
、tq
都以循环方式依赖于另一种类型。
有什么方法可以让它发挥作用吗,通过某种类型的巫术之类的?
我在 Win7 上使用 Haskell 平台 2014.2.0.0 和 GHCi 7.8.3。
我使用 Fix
键入 zipp
的问题(正如我在 Carsten 对 的回答的评论中指出的那样)是没有任何一种语言包含 Fix
类型:
newtype Fix a = Fix { unFix :: Fix a -> [a] }
fixList :: ([a] -> [a]) -> [a]
fixList f = (\g -> f (unFix g g)) $ Fix (\g -> f (unFix g g))
diverges :: [a]
diverges = fixList id
这似乎是一个晦涩的问题,但是用一种完整的语言来实现真的很好,因为这也构成了正式的终止证明。所以让我们在 Agda 中为 zipp
找一个类型。
首先,让我们坚持一段时间Haskell。如果我们手动展开一些固定列表的 zip1
和 zip2
的定义,我们发现所有的展开都有适当的类型,我们可以将 zip1
的任何展开应用于任何展开zip2
,并且类型对齐(我们得到了正确的结果)。
-- unfold zip1 for [1, 0]
f0 k = [] -- zip1 []
f1 k = k 0 f0 -- zip1 [0]
f2 k = k 1 f1 -- zip1 [1, 0]
-- unfold zip2 for [5, 3]
g0 x r = [] -- zip2 []
g1 x r = (x, 3) : r g0 -- zip2 [3]
g2 x r = (x, 5) : r g1 -- zip2 [3, 5]
-- testing
f2 g2 -- [(1, 5), (0, 3)]
f2 g0 -- []
-- looking at some of the types in GHCI
f0 :: t -> [t1]
f1 :: Num a => (a -> (t1 -> [t2]) -> t) -> t
g0 :: t -> t1 -> [t2]
g1 :: Num t1 => t -> ((t2 -> t3 -> [t4]) -> [(t, t1)]) -> [(t, t1)]
我们推测对于zip1
-s 和zip2
-s 的任何特定 组合,类型可以统一,但我们无法表达这与通常的 foldr
一样,因为所有展开都有无数种不同的类型。所以我们现在切换到 Agda。
一些预备知识和依赖项的通常定义 foldr
:
open import Data.Nat
open import Data.List hiding (foldr)
open import Function
open import Data.Empty
open import Relation.Binary.PropositionalEquality
open import Data.Product
foldr :
{A : Set}
(B : List A → Set)
→ (∀ {xs} x → B xs → B (x ∷ xs))
→ B []
→ (xs : List A)
→ B xs
foldr B f z [] = z
foldr B f z (x ∷ xs) = f x (foldr B f z xs)
我们注意到展开的类型取决于待压缩列表的长度,因此我们编写了两个函数来生成这些类型。 A
是第一个列表元素的类型,B
是第二个列表元素的类型,C
是我们到达时忽略的参数的参数列表的末尾。 n
当然是列表的长度。
Zip1 : Set → Set → Set → ℕ → Set
Zip1 A B C zero = C → List (A × B)
Zip1 A B C (suc n) = (A → Zip1 A B C n → List (A × B)) → List (A × B)
Zip2 : Set → Set → Set → ℕ → Set
Zip2 A B C zero = A → C → List (A × B)
Zip2 A B C (suc n) = A → (Zip2 A B C n → List (A × B)) → List (A × B)
我们现在需要证明我们确实可以将任何 Zip1
应用于任何 Zip2
,并得到一个 List (A × B)
作为结果。
unifyZip : ∀ A B n m → ∃₂ λ C₁ C₂ → Zip1 A B C₁ n ≡ (Zip2 A B C₂ m → List (A × B))
unifyZip A B zero m = Zip2 A B ⊥ m , ⊥ , refl
unifyZip A B (suc n) zero = ⊥ , Zip1 A B ⊥ n , refl
unifyZip A B (suc n) (suc m) with unifyZip A B n m
... | C₁ , C₂ , p = C₁ , C₂ , cong (λ t → (A → t → List (A × B)) → List (A × B)) p
英文unifyZip
的类型:"for all A
and B
types and n
and m
natural numbers, there exist some C₁
and C₂
types such that Zip1 A B C₁ n
is a function from Zip2 A B C₂ m
to List (A × B)
".
证明本身很简单;如果我们到达任一拉链的末端,我们将空拉链的输入类型实例化为另一个拉链的类型。使用空类型 (⊥) 表明该参数的类型选择是任意的。在递归的情况下,我们只是将等式证明增加一步迭代。
现在我们可以写zipp
:
zip1 : ∀ A B C (as : List A) → Zip1 A B C (length as)
zip1 A B C = foldr (Zip1 A B C ∘ length) (λ x r k → k x r) (λ _ → [])
zip2 : ∀ A B C (bs : List B) → Zip2 A B C (length bs)
zip2 A B C = foldr (Zip2 A B C ∘ length) (λ y k x r → (x , y) ∷ r k) (λ _ _ → [])
zipp : ∀ {A B : Set} → List A → List B → List (A × B)
zipp {A}{B} xs ys with unifyZip A B (length xs) (length ys)
... | C₁ , C₂ , p with zip1 A B C₁ xs | zip2 A B C₂ ys
... | zxs | zys rewrite p = zxs zys
如果我们眯着眼睛尝试忽略代码中的证明,我们会发现 zipp
在操作上确实与 Haskell 定义相同。事实上,在所有可擦除证明被擦除后,代码变得完全一样。 Agda 可能不会执行此擦除操作,但 Idris 编译器肯定会执行。
(作为旁注,我想知道我们是否可以在融合优化中使用像 zipp
这样的聪明函数。zipp
似乎比 Oleg Kiselyov 的 fold zipping. 更有效但是zipp
似乎没有 System F 类型;也许我们可以尝试将数据编码为依赖消除器(归纳原理)而不是通常的消除器,并尝试融合这些表示?)
应用来自 , I was able to solve it 的见解,通过定义两个相互递归的类型:
-- tr ~ tq -> [(a,b)]
-- tq ~ a -> tr -> [(a,b)]
newtype Tr a b = R { runR :: Tq a b -> [(a,b)] }
newtype Tq a b = Q { runQ :: a -> Tr a b -> [(a,b)] }
zipp :: [a] -> [b] -> [(a,b)]
zipp xs ys = runR (zip1 xs) (zip2 ys)
where
zip1 = foldr (\ x r -> R $ \q -> runQ q x r ) n
n = R (\_ -> [])
zip2 = foldr (\ y q -> Q $ \x r -> (x,y) : runR r q ) m
m = Q (\_ _ -> [])
main = print $ zipp [1..3] [10,20..]
-- [(1,10),(2,20),(3,30)]
从类型等价到类型定义的转换纯粹是机械的,所以也许编译器也可以为我们做这件事!
(有关此内容的上下文,请参阅
我试图仅使用 foldr
得出 zip
的定义:
zipp :: [a] -> [b] -> [(a,b)]
zipp xs ys = zip1 xs (zip2 ys)
where
-- zip1 :: [a] -> tq -> [(a,b)] -- zip1 xs :: tr ~ tq -> [(a,b)]
zip1 xs q = foldr (\ x r q -> q x r ) n xs q
-------- c --------
n q = []
-- zip2 :: [b] -> a -> tr -> [(a,b)] -- zip2 ys :: tq ~ a -> tr -> [(a,b)]
zip2 ys x r = foldr (\ y q x r -> (x,y) : r q ) m ys x r
---------- k --------------
m x r = []
{-
zipp [x1,x2,x3] [y1,y2,y3,y4]
= c x1 (c x2 (c xn n)) (k y1 (k y2 (k y3 (k y4 m))))
--------------- ----------------------
r q
= k y1 (k y2 (k y3 (k y4 m))) x1 (c x2 (c xn n))
---------------------- ---------------
q r
-}
它在纸面上 "works",但给出了两个 "infinite type" 错误:
Occurs check: cannot construct the infinite type:
t1 ~ (a -> t1 -> [(a, b)]) -> [(a, b)] -- tr
Occurs check: cannot construct the infinite type:
t0 ~ a -> (t0 -> [(a, b)]) -> [(a, b)] -- tq
显然,每种类型 tr
、tq
都以循环方式依赖于另一种类型。
有什么方法可以让它发挥作用吗,通过某种类型的巫术之类的?
我在 Win7 上使用 Haskell 平台 2014.2.0.0 和 GHCi 7.8.3。
我使用 Fix
键入 zipp
的问题(正如我在 Carsten 对 Fix
类型:
newtype Fix a = Fix { unFix :: Fix a -> [a] }
fixList :: ([a] -> [a]) -> [a]
fixList f = (\g -> f (unFix g g)) $ Fix (\g -> f (unFix g g))
diverges :: [a]
diverges = fixList id
这似乎是一个晦涩的问题,但是用一种完整的语言来实现真的很好,因为这也构成了正式的终止证明。所以让我们在 Agda 中为 zipp
找一个类型。
首先,让我们坚持一段时间Haskell。如果我们手动展开一些固定列表的 zip1
和 zip2
的定义,我们发现所有的展开都有适当的类型,我们可以将 zip1
的任何展开应用于任何展开zip2
,并且类型对齐(我们得到了正确的结果)。
-- unfold zip1 for [1, 0]
f0 k = [] -- zip1 []
f1 k = k 0 f0 -- zip1 [0]
f2 k = k 1 f1 -- zip1 [1, 0]
-- unfold zip2 for [5, 3]
g0 x r = [] -- zip2 []
g1 x r = (x, 3) : r g0 -- zip2 [3]
g2 x r = (x, 5) : r g1 -- zip2 [3, 5]
-- testing
f2 g2 -- [(1, 5), (0, 3)]
f2 g0 -- []
-- looking at some of the types in GHCI
f0 :: t -> [t1]
f1 :: Num a => (a -> (t1 -> [t2]) -> t) -> t
g0 :: t -> t1 -> [t2]
g1 :: Num t1 => t -> ((t2 -> t3 -> [t4]) -> [(t, t1)]) -> [(t, t1)]
我们推测对于zip1
-s 和zip2
-s 的任何特定 组合,类型可以统一,但我们无法表达这与通常的 foldr
一样,因为所有展开都有无数种不同的类型。所以我们现在切换到 Agda。
一些预备知识和依赖项的通常定义 foldr
:
open import Data.Nat
open import Data.List hiding (foldr)
open import Function
open import Data.Empty
open import Relation.Binary.PropositionalEquality
open import Data.Product
foldr :
{A : Set}
(B : List A → Set)
→ (∀ {xs} x → B xs → B (x ∷ xs))
→ B []
→ (xs : List A)
→ B xs
foldr B f z [] = z
foldr B f z (x ∷ xs) = f x (foldr B f z xs)
我们注意到展开的类型取决于待压缩列表的长度,因此我们编写了两个函数来生成这些类型。 A
是第一个列表元素的类型,B
是第二个列表元素的类型,C
是我们到达时忽略的参数的参数列表的末尾。 n
当然是列表的长度。
Zip1 : Set → Set → Set → ℕ → Set
Zip1 A B C zero = C → List (A × B)
Zip1 A B C (suc n) = (A → Zip1 A B C n → List (A × B)) → List (A × B)
Zip2 : Set → Set → Set → ℕ → Set
Zip2 A B C zero = A → C → List (A × B)
Zip2 A B C (suc n) = A → (Zip2 A B C n → List (A × B)) → List (A × B)
我们现在需要证明我们确实可以将任何 Zip1
应用于任何 Zip2
,并得到一个 List (A × B)
作为结果。
unifyZip : ∀ A B n m → ∃₂ λ C₁ C₂ → Zip1 A B C₁ n ≡ (Zip2 A B C₂ m → List (A × B))
unifyZip A B zero m = Zip2 A B ⊥ m , ⊥ , refl
unifyZip A B (suc n) zero = ⊥ , Zip1 A B ⊥ n , refl
unifyZip A B (suc n) (suc m) with unifyZip A B n m
... | C₁ , C₂ , p = C₁ , C₂ , cong (λ t → (A → t → List (A × B)) → List (A × B)) p
英文unifyZip
的类型:"for all A
and B
types and n
and m
natural numbers, there exist some C₁
and C₂
types such that Zip1 A B C₁ n
is a function from Zip2 A B C₂ m
to List (A × B)
".
证明本身很简单;如果我们到达任一拉链的末端,我们将空拉链的输入类型实例化为另一个拉链的类型。使用空类型 (⊥) 表明该参数的类型选择是任意的。在递归的情况下,我们只是将等式证明增加一步迭代。
现在我们可以写zipp
:
zip1 : ∀ A B C (as : List A) → Zip1 A B C (length as)
zip1 A B C = foldr (Zip1 A B C ∘ length) (λ x r k → k x r) (λ _ → [])
zip2 : ∀ A B C (bs : List B) → Zip2 A B C (length bs)
zip2 A B C = foldr (Zip2 A B C ∘ length) (λ y k x r → (x , y) ∷ r k) (λ _ _ → [])
zipp : ∀ {A B : Set} → List A → List B → List (A × B)
zipp {A}{B} xs ys with unifyZip A B (length xs) (length ys)
... | C₁ , C₂ , p with zip1 A B C₁ xs | zip2 A B C₂ ys
... | zxs | zys rewrite p = zxs zys
如果我们眯着眼睛尝试忽略代码中的证明,我们会发现 zipp
在操作上确实与 Haskell 定义相同。事实上,在所有可擦除证明被擦除后,代码变得完全一样。 Agda 可能不会执行此擦除操作,但 Idris 编译器肯定会执行。
(作为旁注,我想知道我们是否可以在融合优化中使用像 zipp
这样的聪明函数。zipp
似乎比 Oleg Kiselyov 的 fold zipping. 更有效但是zipp
似乎没有 System F 类型;也许我们可以尝试将数据编码为依赖消除器(归纳原理)而不是通常的消除器,并尝试融合这些表示?)
应用来自
-- tr ~ tq -> [(a,b)]
-- tq ~ a -> tr -> [(a,b)]
newtype Tr a b = R { runR :: Tq a b -> [(a,b)] }
newtype Tq a b = Q { runQ :: a -> Tr a b -> [(a,b)] }
zipp :: [a] -> [b] -> [(a,b)]
zipp xs ys = runR (zip1 xs) (zip2 ys)
where
zip1 = foldr (\ x r -> R $ \q -> runQ q x r ) n
n = R (\_ -> [])
zip2 = foldr (\ y q -> Q $ \x r -> (x,y) : runR r q ) m
m = Q (\_ _ -> [])
main = print $ zipp [1..3] [10,20..]
-- [(1,10),(2,20),(3,30)]
从类型等价到类型定义的转换纯粹是机械的,所以也许编译器也可以为我们做这件事!