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
         .....