我最近了解了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
的使用相关的其他评论。
答案 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的let
和where
绑定是递归的: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
重复使用自己的输出(fix
,let g = ((3:)...) ; ps = g ps in 2 : ps
)或为自己创建单独的素数(_Y
,let 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 []
这并不漂亮,因为在这种情况下修复不是一个好的选择(但总有一些人可以更好地编写它)。