Haskell:修复或不修复

时间:2014-02-10 21:15:04

标签: haskell

我最近了解了Data.Function.fix,现在我想在任何地方应用它。例如,每当我看到递归函数时,我想“fix”它。所以基本上我的问题是我应该在何时何地使用它。

使其更具体:

1)假设我有以下代码来n

的因子分解
f n = f' n primes
  where
    f' n (p:ps) = ...
    -- if p^2<=n: returns (p,k):f' (n `div` p^k) ps for k = maximum power of p in n
    -- if n<=1: returns []
    -- otherwise: returns [(n,1)]

如果我用fix重写它,我会获得一些东西吗?失去什么?是否有可能通过将显式递归重写为fix - 版本我将解决,反之亦然会创建堆栈溢出?

2)处理列表时,有几种解决方案:递归/修复,foldr / foldl / foldl',可能还有其他的东西。关于何时使用每种方法,是否有任何一般指导/建议?例如,您是否会使用foldr在无限的素数列表上重写上述代码?

这里可能还有其他重要问题没有涉及。我们也欢迎任何与fix的使用相关的其他评论。

4 个答案:

答案 0 :(得分:19)

通过以明确的fix形式书写可以获得的一点是递归是“开放的”。

factOpen :: (Integer -> Integer) -> Integer -> Integer
factOpen recur 0 = 1
factOpen recur n = n * recur (pred n)

我们可以使用fix定期fact返回

fact :: Integer -> Integer
fact = fix factOpen

这是有效的,因为fix有效地将函数本身作为其第一个参数传递。但是,通过使递归保持打开状态,我们可以修改哪个函数被“传回”。使用此属性的最佳示例是使用类似memoFix from the memoize package的内容。

factM :: Integer -> Integer
factM = memoFix factOpen

现在factM内置了memoization。

实际上,我们有开放式递归要求我们将递归位作为一阶事物。递归绑定是Haskell允许在语言级别递归的一种方式,但我们可以构建其他更专业的表单。

答案 1 :(得分:7)

我想提一下fix的另一种用法;假设您有一个由加法,负数和整数文字组成的简单语言。也许您编写了一个解析器,它使用String并输出Tree

data Tree = Leaf String | Node String [Tree]
parse :: String -> Tree

-- parse "-(1+2)" == Node "Neg" [Node "Add" [Node "Lit" [Leaf "1"], Node "Lit" [Leaf "2"]]]

现在,您要将树评估为单个整数:

fromTree (Node "Lit" [Leaf n]) = case reads n of {[(x,"")] -> Just x; _ -> Nothing}
fromTree (Node "Neg" [e])      = liftM negate (fromTree e) 
fromTree (Node "Add" [e1,e2])  = liftM2 (+) (fromTree e1) (fromTree e2)

假设其他人决定扩展该语言;他们想要增加乘法。他们必须能够访问原始源代码。他们可以尝试以下方法:

fromTree' (Node "Mul" [e1, e2]) = ...
fromTree' e                     = fromTree e

但是Mul只能在表达式的顶层出现一次,因为对fromTree的调用不会知道Node "Mul"的情况。 Tree "Neg" [Tree "Mul" a b]无效,因为原始fromTree没有"Mul"的模式。但是,如果使用fix

编写相同的函数
fromTreeExt :: (Tree -> Maybe Int) -> (Tree -> Maybe Int)
fromTreeExt self (Node "Neg" [e]) = liftM negate (self e)
fromTreeExt .... -- other cases

fromTree = fix fromTreeExt

然后可以扩展语言:

fromTreeExt' self (Node "Mul" [e1, e2]) = ...
fromTreeExt' self e                     = fromTreeExt self e

fromTree' = fix fromTreeExt'

现在,扩展的fromTree'会正确评估树,因为self中的fromTreeExt'指的是整个函数,包括“Mul”情况。

使用此方法here(上面的示例是本文中使用的一个密切适应的版本)。

答案 2 :(得分:2)

注意_Y f = f (_Y f)(递归,值 - 复制)和fix f = x where x = f x(corecursion,reference - sharing)之间的区别。

Haskell的letwhere绑定是递归的:LHS上的相同名称和RHS指的是同一个实体。引用是共享

_Y的定义中,没有共享(除非编译器执行公共子表达式消除的积极优化)。这意味着它描述了递归,其中通过应用原始的副本来实现重复,就像在recursive function creating its own copies的经典隐喻中一样。另一方面,Corecursion在引用同一实体时依赖于共享。

一个例子,由

计算的素数
2 : _Y ((3:) . gaps 5 . _U . map (\p-> [p*p, p*p+2*p..]))

-- gaps 5 == ([5,7..] \\)
-- _U     == sort . concat

重复使用自己的输出(fixlet g = ((3:)...) ; ps = g ps in 2 : ps)或为自己创建单独的素数(_Ylet g () = ((3:)...) (g ()) in 2 : g ())。

另见:


或者,通过阶乘函数的通常例子,

gen rec n = n<2 -> 1 ; n * rec (n-1)            -- "if" notation

facrec   = _Y gen 
facrec 4 = gen (_Y gen) 4 
         = let {rec=_Y gen} in (\n-> ...) 4
         = let {rec=_Y gen} in (4<2 -> 1 ; 4*rec 3)
         = 4*_Y gen 3
         = 4*gen (_Y gen) 3
         = 4*let {rec2=_Y gen} in (3<2 -> 1 ; 3*rec2 2) 
         = 4*3*_Y gen 2                         -- (_Y gen) recalculated
         .....

fac      = fix gen 
fac 4    = (let f = gen f in f) 4             
         = (let f = (let {rec=f} in (\n-> ...)) in f) 4
         = let {rec=f} in (4<2 -> 1 ; 4*rec 3)  -- f binding is created
         = 4*f 3
         = 4*let {rec=f} in (3<2 -> 1 ; 3*rec 2)  
         = 4*3*f 2                              -- f binding is reused
         .....

答案 3 :(得分:1)

1)fix只是一个函数,当你使用一些递归时它会改进你的代码。它使您的代码更漂亮。例如使用访问:Haskell Wikibook - Fix and recursion

2)你知道什么是折叠?看起来像foldr在分解中没用(或者我不明白你的意思是什么)。 这是一个没有修复的主要因子分析:

fact xs = map (\x->takeWhile (\y->y/=[]) x) . map (\x->factIt x) $ xs
 where factIt n = map (\x->getFact x n []) [2..n]
   getFact i n xs
    | n `mod` i == 0 = getFact i (div n i) xs++[i]
    | otherwise      = xs

和修复(这完全像以前一样):

fact xs = map (\x->takeWhile (\y->y/=[]) x) . map (\x->getfact x) $ xs
  where getfact n  = map (\x->defact x n) [2..n]
       defact i n  = 
        fix (\rec j k xs->if(mod k j == 0)then (rec j (div k j) xs++[j]) else xs ) i n []

这并不漂亮,因为在这种情况下修复不是一个好的选择(但总有一些人可以更好地编写它)。