我有一个函数,它根据迭代计算一个固定点:
equivalenceClosure :: (Ord a) => Relation a -> Relation a
equivalenceClosure = fst . List.head -- "guaranteed" to exist
. List.dropWhile (uncurry (/=)) -- removes pairs that are not equal
. U.List.pairwise (,) -- applies (,) to adjacent list elements
. iterate ( reflexivity
. symmetry
. transitivity
)
请注意,我们可以从中抽象出来:
findFixedPoint :: (a -> a) -> a -> a
findFixedPoint f = fst . List.head
. List.dropWhile (uncurry (/=)) -- dropWhile we have not reached the fixed point
. U.List.pairwise (,) -- applies (,) to adjacent list elements
. iterate
$ f
这个功能可以用修复方式编写吗?似乎应该有一个从这个方案转变为具有修复的东西,但我没有看到它。
答案 0 :(得分:10)
这里有相当多的内容,从懒惰评估的机制到固定点的定义到方法找到一个固定点。简而言之,我相信您可能会错误地将lambda演算中的功能应用程序的固定点与您的需求进行交换。
注意找到定点(利用iterate
)的实现需要函数应用程序序列的起始值可能会有所帮助。将此与fix
函数进行对比,该函数不需要这样的起始值(作为一个抬头,类型已经提供了这个:findFixedPoint
类型为(a -> a) -> a -> a
,而fix
类型(a -> a) -> a
)。这本质上是因为这两个函数做了微妙的不同。
让我们深入研究一下。首先,我应该说你可能需要提供更多信息(例如你的成对实现),但是先尝试一下,我的(可能是有缺陷的)实现我认为你想要的成对,您的findFixedPoint
函数在结果中与fix
相同,仅适用于某类函数
我们来看看一些代码:
{-# LANGUAGE RankNTypes #-}
import Control.Monad.Fix
import qualified Data.List as List
findFixedPoint :: forall a. Eq a => (a -> a) -> a -> a
findFixedPoint f = fst . List.head
. List.dropWhile (uncurry (/=)) -- dropWhile we have not reached the fixed point
. pairwise (,) -- applies (,) to adjacent list elements
. iterate f
pairwise :: (a -> a -> b) -> [a] -> [b]
pairwise f [] = []
pairwise f (x:[]) = []
pairwise f (x:(xs:xss)) = f x xs:pairwise f xss
将此与fix
:
fix :: (a -> a) -> a
fix f = let x = f x in x
你会注意到我们发现了一种非常不同的定点(即我们滥用延迟评估来为mathematical sense中的函数应用生成一个固定点,我们只停止评估 iff * 结果函数应用于自身,评估相同的函数。)
为了说明,我们定义一些函数:
lambdaA = const 3
lambdaB = (*)3
让我们看看fix
和findFixedPoint
:
*Main> fix lambdaA -- evaluates to const 3 (const 3) = const 3
-- fixed point after one iteration
3
*Main> findFixedPoint lambdaA 0 -- evaluates to [const 3 0, const 3 (const 3 0), ... thunks]
-- followed by grabbing the head.
3
*Main> fix lambdaB -- does not stop evaluating
^CInterrupted.
*Main> findFixedPoint lambdaB 0 -- evaluates to [0, 0, ...thunks]
-- followed by grabbing the head
0
现在如果我们不能指定起始值,fix
用于什么?事实证明,通过将fix
添加到lambda演算中,我们可以指定递归函数的评估。考虑fact' = \rec n -> if n == 0 then 1 else n * rec (n-1)
,我们可以将fact'
的固定点计算为:
*Main> (fix fact') 5
120
在评估(fix fact')
时重复应用fact'
本身,直到我们到达相同的 函数,然后我们使用值{{1 }}。我们可以在:
5
那么这一切意味着什么呢?根据您正在处理的功能,您不一定能够使用 fix fact'
= fact' (fix fact')
= (\rec n -> if n == 0 then 1 else n * rec (n-1)) (fix fact')
= \n -> if n == 0 then 1 else n * fix fact' (n-1)
= \n -> if n == 0 then 1 else n * fact' (fix fact') (n-1)
= \n -> if n == 0 then 1
else n * (\rec n' -> if n' == 0 then 1 else n' * rec (n'-1)) (fix fact') (n-1)
= \n -> if n == 0 then 1
else n * (if n-1 == 0 then 1 else (n-1) * fix fact' (n-2))
= \n -> if n == 0 then 1
else n * (if n-1 == 0 then 1
else (n-1) * (if n-2 == 0 then 1
else (n-2) * fix fact' (n-3)))
= ...
来计算所需的固定点类型。据我所知,这取决于所讨论的功能。并非所有函数都具有由fix
计算的固定点!
*我避免谈论领域理论,因为我认为它只会混淆一个已经很微妙的话题。如果您感到好奇,fix
会找到一个某些 种类的固定点,即该函数指定的poset的最小可用固定点。
答案 1 :(得分:2)
仅供记录,可以使用findFixedPoint
定义功能fix
。
正如Raeez所指出的,递归函数可以用fix
来定义。
您感兴趣的函数可以递归定义为:
findFixedPoint :: Eq a => (a -> a) -> a -> a
findFixedPoint f x =
case (f x) == x of
True -> x
False -> findFixedPoint f (f x)
这意味着我们可以将其定义为fix ffp
,其中ffp
为:
ffp :: Eq a => ((a -> a) -> a -> a) -> (a -> a) -> a -> a
ffp g f x =
case (f x) == x of
True -> x
False -> g f (f x)
对于一个具体的例子,我们假设f
被定义为
f = drop 1
很容易看出,对于每个有限列表l
,我们都有findFixedPoint f l == []
。
当“值参数”为[]时,fix ffp
的工作方式如下:
(fix ffp) f []
= { definition of fix }
ffp (fix ffp) f []
= { f [] = [] and definition of ffp }
[]
另一方面,如果“值参数”是[42],我们将:
fix ffp f [42]
= { definition of fix }
ffp (fix ffp) f [42]
= { f [42] =/= [42] and definition of ffp }
(fix ffp) f (f [42])
= { f [42] = [] }
(fix ffp) f []
= { see above }
[]