Haskell 仅使用foldr定义zip时出现无限类型错误;能修好吗?

Haskell 仅使用foldr定义zip时出现无限类型错误;能修好吗?,haskell,types,fold,Haskell,Types,Fold,(有关本文的上下文,请参见) 我试图仅使用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

(有关本文的上下文,请参见)

我试图仅使用
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
-}
它在纸上“起作用”,但会产生两个“无限类型”错误:

显然,每种类型的
tr
tq
,都以循环的方式相互依赖

有没有办法让它工作,通过某种巫术或别的什么


我将Haskell Platform 2014.2.0.0与Win7上的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中为
zip
找到一个类型

首先,让我们和哈斯克尔呆一会儿。如果我们为一些固定列表手动展开
zip1
zip2
的定义,我们会发现所有的展开都有适当的类型,并且我们可以将
zip1
的任何展开应用于
zip2
的任何展开,并且类型对齐(我们得到了正确的结果)

我们推测,对于
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
,并返回一个
列表(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
类型:“对于所有
A
B
类型和
n
m
自然数,都存在一些
C₁
C₂类型,以便
Zip1 A B C₁ n
是来自
Zip2 a B C的函数₂ m
列表(A×B)

证据本身是直截了当的;如果我们碰到任何一个拉链的末端,我们将空拉链的输入类型实例化为另一个拉链的类型。空类型的使用(⊥) 表示该参数的类型选择是任意的。在递归情况下,我们只需通过一步迭代就可以完成等式证明

现在我们可以编写
zip

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的效率更高,但
zipp
似乎没有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)]

从类型等价到类型定义的转换完全是机械的,所以也许编译器也可以为我们这样做!

Hi-我在原始问题的答案中添加了默认技巧,向您展示如何在那里修复您的
zip
-您也应该能够使用它来实现这一点(但你必须对类型、定义等有更多的困惑)@CarstenKönig没关系;但我这里不需要相互递归类型吗?它与你在那里添加的有很大不同吗?(顺便说一句,我假设“编织”而不是压缩OP的代码是他们的错误)我并没有真正尝试过它,可能更容易用一个或两个来欺骗不同的类型,使用奇怪的
zipCat
,然后将其输出到
unzip
inline(如果你明白我的意思的话)@WillNess这就是我想要的(相当于
(\ab->concat(zipWith(\ab->[a,b])ab))
),因为我注意到如果没有元组构造函数,它看起来会很好!不是真的试图实现
zip
,而是获得了一个insight.Neat。另外,我也不知道为什么zip会更有效。@Viclib Oleg的版本是O(n^2),而
zip
是O(n).实际上,
zip
和你的
zip
确实激起了我的兴趣,因为我最初认为在O(N)中不可能做到这一点,我怀疑函数有点错误。但是,唉,我设法在Agda中证明了它们是正确的。你的古怪的非类型lambda术语竟然有一个类型,这很清楚。我认为值得进一步研究
zipp
和其他类似的函数,我认为这里有更多的东西在你的回答中,他提到了他的前一个版本,“带有递归类型”.我想他指的是。在靠近结尾的地方有一个
szip
函数;看起来我的代码做了一些与之非常相似的事情。这里讨论了这与
zip
的融合优化的可能相关性,正如你提到的。@user3237465这是“标准”O(N^2)解决方案;问题和我的答案中的实现是O(N),其中
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
-- 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)]