Haskell:常见的Corecursive谬误

时间:2011-06-08 03:05:33

标签: haskell graph graph-algorithm

所以今晚我正在考虑图距离算法,并提出了这个问题 当我开车的时候:

module GraphDistance where
import Data.Map

distance :: (Ord a) => a -> Map a [a] -> Map a Int
distance a m = m' 
  where m' = mapWithKey d m
        d a' as = if a == a' then 0 else 1 + minimum (Prelude.map (m'!) as)

起初,我为自己感到骄傲,因为它看起来很优雅。 但后来我意识到它不起作用 - 核心运行可能会卡住 在循环中。

我必须编写代码才能说服自己:

ghci> distance 1 $ fromList [(0,[1]),(1,[0,2]),(2,[1,3]),(3,[2])]
fromList [(0,1),(1,0),(2,^CInterrupted.

但现在我觉得我已经考虑过了。

是否有常见错误和反模式列表 在处理我可以阅读的核心数据时, 所以我可以训练我的大脑去思考?经验 通过非核心思考,我已经很好地训练了我 数据,但如果可以的话,我想向别人学习错误。

1 个答案:

答案 0 :(得分:13)

嗯,在处理corecursive数据时,实际上只有一个基本错误,那就是不小心使用递归!

Corecursion意味着在某种意义上数据是以递增方式生成的。你的图形距离函数在这里是有启发性的,因为它似乎应该工作,所以想想增量部分应该在哪里:起点是从节点到自身的距离0,否则一个更大比它自己的近邻之间的最小距离。因此,我们希望每个距离值是增量的,这意味着我们需要它们本身适当地是自发的。

由于(+)minimum的组合,问题的递归正在发生:当找到最小值时,1将始终小于1 + n,无需担心n是什么......但是如果不计算整个值,就无法比较Int

简而言之,该算法希望能够只根据需要比较(1 +)应用0的次数;也就是说,它希望使用“零”和“后继”定义惰性自然数。

看哪:

data Nat = Z | S Nat

natToInt :: Nat -> Int
natToInt Z = 0
natToInt (S n) = 1 + natToInt n

instance Show Nat where show = show . natToInt

instance Eq Nat where
    Z == Z = True
    (S n1) == (S n2) = n1 == n2
    _ == _ = False

    Z /= Z = False
    (S n1) /= (S n2) = n1 /= n2
    _ /= _ = True


instance Ord Nat where
    compare Z Z = EQ
    compare Z (S _) = LT
    compare (S _) Z = GT
    compare (S n1) (S n2) = compare n1 n2

然后在GHCi中:

> distance 1 $ fromList [(0,[1]),(1,[0,2]),(2,[1,3]),(3,[2])]
fromList [(0,1),(1,0),(2,1),(3,2)]

证明您的算法有效 [0] ;你的实现是不正确的。


现在,稍微改变一下,让我们将您的算法应用到不同的图表中:

> distance 1 $ fromList [(0,[1]),(1,[0]),(2,[3]),(3,[2])]

......我们对此有何看法?节点1与节点2或3的距离是多少?

在GHCi中运行它有明显的结果:

fromList [(0,1),(1,0),(2,^CInterrupted.

然而,算法在此图上正常工作。你能看到问题吗?为什么它挂在GHCi?


总之,您需要清楚地区分两种不能自由混合的形式:

  • 懒惰,可能是无限数据,以核心方式生成
  • 递归消耗的有限数据

两种形式都可以以结构无关的方式进行转换(例如,map可以在有限和无限列表上工作)。 Codata可以通过核心递归算法逐步消耗;数据可以递归生成,受递归算法的限制。

不能做的是递归地使用codata(例如,左折叠无限列表)或者生成数据(由于懒惰而在Haskell中很少见)。

[0] :实际上,它会在某些输入上失败(例如,某些断开连接的图形),但这是一个不同的问题。