为什么Haskell不接受我的组合" zip"定义?

时间:2015-04-26 15:59:36

标签: haskell fold

这是教科书的zip功能:

zip :: [a] -> [a] -> [(a,a)]
zip [] _ = []
zip _ [] = []
zip (x:xs) (y:ys) = (x,y) : zip xs ys

我早些时候在#haskell问过" zip"可以使用" foldr"单独,没有递归,没有模式匹配。经过一番思考后,我们注意到可以使用continuation来消除递归:

zip' :: [a] -> [a] -> [(a,a)]
zip' = foldr cons nil
    where
        cons h t (y:ys) = (h,y) : (t ys)
        cons h t []     = []
        nil             = const []

我们仍然留有模式匹配。在进行了一些神经元烘烤之后,我想出了一个我认为合乎逻辑的不完整答案:

zip :: [a] -> [a] -> [a]
zip a b = (zipper a) (zipper b) where
    zipper = foldr (\ x xs cont -> x : cont xs) (const [])

它会返回一个平面列表,但会进行压缩。我确信它有道理,但Haskell抱怨这种类型。我继续在一个无类型的lambda计算器上测试它,它工作。为什么Haskell不能接受我的功能?

错误是:

zip.hs:17:19:
    Occurs check: cannot construct the infinite type:
      t0 ~ (t0 -> [a]) -> [a]
    Expected type: a -> ((t0 -> [a]) -> [a]) -> (t0 -> [a]) -> [a]
      Actual type: a
                   -> ((t0 -> [a]) -> [a]) -> (((t0 -> [a]) -> [a]) -> [a]) -> [a]
    Relevant bindings include
      b ∷ [a] (bound at zip.hs:17:7)
      a ∷ [a] (bound at zip.hs:17:5)
      zip ∷ [a] -> [a] -> [a] (bound at zip.hs:17:1)
    In the first argument of ‘foldr’, namely ‘cons’
    In the expression: ((foldr cons nil a) (foldr cons nil b))

zip.hs:17:38:
    Occurs check: cannot construct the infinite type:
      t0 ~ (t0 -> [a]) -> [a]
    Expected type: a -> (t0 -> [a]) -> t0 -> [a]
      Actual type: a -> (t0 -> [a]) -> ((t0 -> [a]) -> [a]) -> [a]
    Relevant bindings include
      b ∷ [a] (bound at zip.hs:17:7)
      a ∷ [a] (bound at zip.hs:17:5)
      zip ∷ [a] -> [a] -> [a] (bound at zip.hs:17:1)
    In the first argument of ‘foldr’, namely ‘cons’
    In the fourth argument of ‘foldr’, namely ‘(foldr cons nil b)’

3 个答案:

答案 0 :(得分:6)

至于为什么不接受你的定义:看看这个:

λ> :t \ x xs cont -> x : cont xs
 ... :: a -> r -> ((r -> [a]) -> [a])

λ> :t foldr
foldr :: (a' -> b' -> b') -> b' -> [a'] -> b'

所以,如果你想使用第一个函数作为foldr的参数,你可以得到(如果你匹配foldr的第一个参数的类型

a' := a
b' := r
b' := (r -> [a]) -> [a]

当然是一个问题(r(r -> [a]) -> [a]相互递归,并且都应该等于b'

这就是编译器告诉你的事情

如何修复它

您可以使用

修复您的想法
newtype Fix a t = Fix { unFix :: Fix a t -> [a] }

借用形成original use

有了这个,你可以写:

zipCat :: [a] -> [a] -> [a]
zipCat a b = (unFix $ zipper a) (zipper b) where
  zipper = foldr foldF (Fix $ const [])
  foldF x xs = Fix (\ cont -> x : (unFix cont $ xs))

你得到:

λ> zipCat [1..4] [5..8]
[1,5,2,6,3,7,4,8]

这是(我认为)你想要的。

但是显而易见,这两个列表都需要属于同一类型,所以我不知道这是否真的对你有帮助

答案 1 :(得分:2)

我们可以通过定义一个能为我们做的函数来消除显式模式匹配。

是作弊吗?如果允许maybebool,则不是这样;那么我们也应该允许list

list n c []     = n
list n c (x:xs) = c x xs
只是一样;这样我们就可以在zip'定义中

cons h t = list [] (\y ys -> (h,y) : t ys)

或者例如

         = list [] (uncurry ((:).(h,).fst <*> t.snd))
         = list [] (curry $ uncurry (:) . ((h,) *** t))
         = list [] (flip ((.) . (:) . (h,)) t)

如果您喜欢那种东西。

关于你的错误,“无限类型”通常表示自我应用;事实上,无论你的zipper返回什么,你都会在你的

中自我应用它
zip a b = (zipper a) (zipper b)  where ....

我尝试调整你的定义并提出

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
-}

它似乎在纸上正确缩小,但我在这里仍然遇到无限类型错误。

现在没有(立即明显的)自我应用,但第一个zip获得的延续类型取决于第一个zip本身的类型;所以仍然存在循环依赖:tq位于tq ~ a -> tr -> [(a,b)] ~ a -> (tq -> [(a,b)]) -> [(a,b)]中类型等价的两侧。

确实这是我得到的两种类型错误,(第一种是关于tr类型),

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

在使用带有continuation的foldr的通常定义中,这些continuation的类型是独立的;这就是它在那里工作的原因,我想。

答案 2 :(得分:2)

我可以为你提供一个稍微不同的视角(我认为),以达到与Carsten相似的解决方案(但更简单的类型)。

这是你的代码,为你的“编织拉链”(我正在为tr的类型r写{,tq代表{{1}的类型我总是使用“q”作为r定义中组合函数的递归结果参数,作为助记符设备):

foldr

所以,这是无限型。 Haskell不允许任何类型的变量(这是变量所代表的类型)。

但是Haskell的数据类型实际上确实承认了递归。列表,树木等 - 所有常见的类型都是递归的。 允许:

zipw :: [a] -> [a] -> [a]
zipw xs ys = (zipper xs) (zipper ys) where
    zipper xs q = foldr (\ x r q -> x : q r) (const []) xs q
                        --- c -------------- --- n ----

 -- zipper [x1,x2,x3] (zipper ys) =
 -- c x1 (c x2 (c x3 n)) (zipper ys)
         --- r --------  --- q -----  tr ~ tq ; q r :: [a]
                                      --     => r r :: [a]
                                      -- => r :: tr -> [a] 
                                      --   tr ~  tr -> [a]    

这里我们在等式的两边都有相同的类型,就像我们在等价类型data Tree a = Branch (Tree a) (Tree a) 的两边都有tr一样。但它是一种特定类型,而不是任意类型。

所以我们按照上面的“等式”声明:

tr ~ tr -> [a]

什么是newtype TR a = Pack { unpack :: TR a -> [a] } -- unpack :: TR a -> TR a -> [a] 类型?它是Tree a的“东西”,是Branch。给定的树不必无限构造,因为Tree a也具有类型undefined

什么是Tree a类型?它是TR a的“内容”,即TR a -> [a]。给定的TR a不必无限构造,因为TR a也可以是const []类型。

我们的崇拜递归类型TR a已成为真正的递归类型定义tr ~ tr -> [a],隐藏在数据构造函数newtype TR a = Pack { TR a -> [a] }之后(由编译器将其驱逐,感谢正在使用Pack个关键字,但这是一个无关紧要的细节;它也适用于newtype

Haskell在这里为我们处理递归。类型理论家喜欢用data和诸如此类的东西来处理这个问题。但是Haskell用户已经用这种语言向他们提供了这个功能。我们无需了解它是如何实现的,以便能够使用它。在我们想要自己构建它之前,无需重新发明轮子。

因此,Fix的类型为zipper xs;现在变为tr,所以这就是新的TR a必须返回的 - “打包”列表生成函数。 zipper xs组合函数必须返回foldr调用返回的内容(通过zipper定义的优点)。要应用打包函数,我们现在首先需要foldr

unpack