【问题标题】:Why Haskell doesn't accept my combinatoric "zip" definition?为什么 Haskell 不接受我的组合“zip”定义?
【发布时间】:2015-04-26 15:59:36
【问题描述】:

这是教科书的zip功能:

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

我早些时候在#haskell 上询问过“zip”是否可以单独使用“foldr”来实现,没有递归,没有模式匹配。经过一番思考,我们注意到可以使用延续来消除递归:

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)’

【问题讨论】:

标签: haskell fold


【解决方案1】:

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

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

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

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

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]

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

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

【讨论】:

  • 使用Fix 等同于使用一般递归或定点组合器,所以我认为这与OP 的“无递归”标准不一致。
  • @AndrásKovács 很有可能是的(尽管您可能会争论类型与函数,或者 foldr 也可能是递归的,...)-您是否看到另一种让编译器接受OP 的 zip 不会偏离代码太多?
  • 我还没有查看 OP 的代码...至于 foldr,它几乎是非递归的,因为它对应于 System F 中的 Church 编码列表(和一切都可以证明在那里终止),以及类型论中列表的计算规则。
  • @AndrásKovács tbh:我认为这不是争论的地方,但 AFAIK foldr 仍然是 implemented 以递归方式(在 Haskell 中)-但这又不是重点?
  • @AndrásKovács(和其他人......)嗨,我添加了一个新答案,也许你想看看。它使用了更简单的递归类型(t 实际上是多余的),而且 Haskell 已经有了递归类型,所以我不需要自己考虑“修复”!
【解决方案2】:

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

这是你的代码,你的“编织拉链”(我写tr代表“r的类型”,同样tq代表“q的类型”;我总是使用“ r" 用于foldr 定义中组合函数的递归结果 参数,作为助记符):

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]    

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

但 Haskell 的数据类型确实承认递归。列表、树等——所有常见的类型都是递归的。这允许的:

data Tree a = Branch (Tree a) (Tree a)

这里我们确实在等式的两边都有相同的类型,就像我们在类型等价的两边都有trtr ~ 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 关键字,编译器将删除它,但这是一个无关紧要的细节;它也适用于data)。

Haskell 在这里为我们处理递归。类型理论家喜欢自己处理这个问题,Fix 等等;但是 Haskell 用户已经可以使用该语言。我们不必了解它是如何实现的,就可以使用它。在我们想自己建造之前,无需重新发明轮子。

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

zipw :: [a] -> [a] -> [a]
zipw xs ys = unpack (zipper xs) (zipper ys)
    where
    zipper :: [a] -> TR a
    zipper = foldr (\ x r -> Pack $ \q -> x : unpack q r)
                   (Pack $ const [])

【讨论】:

  • 很好 - 是的,t 是多余的 - tbh:我只是使用了快速的 copy&paste 编译器输出和 Fix 它的解决方案;)
  • @CarstenKönig 谢谢; :) 我想在另一个问题中使用这种洞察力;现在没有运气。类型更复杂...
  • 是的,我猜是这样 - 但它应该很容易翻译 [a,b,c,d,e] -> [(a,b),(c,d),...]并混合一些 ADT(想到任何一个)
  • @CarstenKönig 我明白了!我定义了两种相互递归的类型:ideone.com/tgAK0A。很快就会发布答案!
  • 无意冒犯 - 如果你添加另一个答案,我没有问题 - 有 4 个答案,其中 3 个来自同一个人,这看起来很奇怪 - 但恭喜 - 干得好
【解决方案3】:

我们可以通过定义一个为我们执行此操作的函数来消除显式模式匹配。

这是作弊吗?如果maybebool 被允许,则不是这样;那么我们也应该允许list(也在extraData.List.Extra中),

list :: b -> (a -> [a] -> b) -> [a] -> b 
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)
         = list [] ((. t) . (:) . (h,))

如果你喜欢这种东西。

关于你的错误,“无限类型”通常表示自我应用;事实上,无论你的 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 本身的类型;所以仍然存在循环依赖:tqtq ~ a -&gt; tr -&gt; [(a,b)] ~ a -&gt; (tq -&gt; [(a,b)]) -&gt; [(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

在使用foldr 和延续的通常定义中,这些延续的类型是独立的;我猜这就是它在那里工作的原因。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-01-15
    • 2020-06-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-28
    • 2023-03-07
    相关资源
    最近更新 更多