Haskell 哈斯克尔:修复还是不修复
我最近学到了,现在我想把它应用到任何地方。例如,每当我看到递归函数时,我都想“Haskell 哈斯克尔:修复还是不修复,haskell,fixpoint-combinators,Haskell,Fixpoint Combinators,我最近学到了,现在我想把它应用到任何地方。例如,每当我看到递归函数时,我都想“修复它”。所以基本上我的问题是我应该在哪里和什么时候使用它 更具体地说: 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: re
修复它”。所以基本上我的问题是我应该在哪里和什么时候使用它
更具体地说:
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)]
fn=f'n素数
哪里
f'n(p:ps)=。。。
--如果p^2以显式的fix
ed形式写入,可以得到一件事,那就是递归保持“打开”
我们可以使用fix
返回常规fact
fact :: Integer -> Integer
fact = fix factOpen
这是因为fix
有效地将函数本身作为其第一个参数传递。然而,通过让递归保持打开状态,我们可以修改哪个函数被“传递回”。使用此属性的最佳示例是使用以下内容
现在factM已经内置了记忆功能
实际上,我们有一种开放式递归,它要求我们将递归位作为一阶对象进行插补。递归绑定是Haskell允许在语言级别进行递归的一种方式,但我们可以构建其他更专业的表单。1)修复只是一个函数,它可以在使用某些递归时改进代码。它使您的代码更漂亮。例如,用法访问:
2) 你知道foldr做了什么吗?似乎foldr在因子分解中没有用处(或者我不明白你的意思)。
下面是一个不带fix的素因式分解:
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
使用fix(这与前面的工作方式完全相同):
这并不漂亮,因为在这种情况下,fix并不是一个好的选择(但总有人能写得更好)。我想提一下fix的另一个用法fix
;假设您有一种由加法、负数和整数文字组成的简单语言。也许您已经编写了一个解析器,它接受一个字符串
,并输出一个树
:
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
的调用将不知道节点“Mul”的大小写<代码>树“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'
将正确地计算树,因为fromTreeExt'
中的self
引用整个函数,包括“Mul”情况
使用这种方法(上面的例子是本文中使用的一个非常合适的版本)。注意\u Y f=f(\u Y f)
(递归,值-复制)和修复f=x,其中x=f x
(corecursion,引用-共享)之间的区别
Haskell的让
和其中
绑定是递归的:LHS和RHS上的相同名称表示相同的实体。参考文献是共享的
在\u Y
的定义中,没有共享(除非编译器对公共子表达式执行积极的优化)。这意味着它描述了递归,在递归中,重复是通过应用原始文件的副本来实现的,就像一个经典的循环隐喻一样。另一方面,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
.....
genrecn=n1;n*rec(n-1)——“如果”符号
facrec=_ygen
facrec 4=第4代
=let{rec=_ygen}in(\n->…)4
=let{rec=_ygen}in(41;4*rec3)
=4*Y第3代
=4*gen(_ygen)3
=4*let{rec2=_ygen}in(31;3*rec2)
=4*3*Y第2代--(Y第2代)重新计算
.....
fac=固定发电机
fac4=(设f=f中的genf)4
=(let f=(let{rec=f}in(\n->…)in f)4
=让{rec=f}in(41;4*rec3)--创建f绑定
=4*f3
=4*let{rec=f}in(31;3*rec 2)
=4*3*f 2--f绑定被重用
.....
“我最近学习了Data.Function.fix,现在我觉得我想在任何地方都应用它。”这让你成为童子军Haskell程序员-如果可以,你应该使用foldr
和foldl'
,fix
或显式递归。后者功能较弱,因此代码的读者可以从中推断出更多属性。@Stephentelley这是一个很好的链接,但我已经看到了!事实上,在我第一次看到它(并仔细研究了它!)的时候,我对其中的两个实现有另一个问题,但也许其他时间。。。无论如何,“男孩童子军”的实现正是我现在在大多数代码中“倾向于”做的@托梅利斯如果你能抽出时间来详细说明这一点,我将不胜感激。关于我的第一个问题,Joseph已经在下面给了我一个很好的提示,我仍然希望根据大师们的经验建立一个更通用的“指导原则”。注意\u Y f=f(\u Y f)
(递归,值-复制)和修复f=x,其中x=f x
(corecursion,引用-共享)之间的区别。非常有趣。而且强大!伟大的这和约瑟夫的回答是两个很好的例子,说明fix似乎有帮助
fromTreeExt' self (Node "Mul" [e1, e2]) = ...
fromTreeExt' self e = fromTreeExt self e
fromTree' = fix fromTreeExt'
2 : _Y ((3:) . gaps 5 . _U . map (\p-> [p*p, p*p+2*p..]))
-- gaps 5 == ([5,7..] \\)
-- _U == sort . concat
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
.....