假设一分钟我们认为以下是一个好主意:
data Fold x y = Fold {start :: y, step :: x -> y -> y}
fold :: Fold x y -> [x] -> y
在此方案下,可以通过使用适当的length
对象作为参数调用sum
来实现fold
或Fold
等函数。
现在,假设你想做一些聪明的优化技巧。特别是,假设你想写
unFold :: ([x] -> y) -> Fold x y
规则RULES
pragma应该相对容易fold . unFold = id
。但有趣的问题是......我们实际上可以实施 unFold
吗?
显然,您可以使用RULES
来应用任意代码转换,无论它们是否保留代码的原始含义。但你真的可以写一个unFold
实现,它实际上做了类型签名所暗示的吗?
答案 0 :(得分:12)
不,这是不可能的。证明:让
f :: [()] -> Bool
f[] = False
f[()] = False
f _ = True
首先,对于f' = unFold f
,我们必须start f' = False
,因为当折叠空列表时,我们直接获取起始值。然后,我们必须要求step f' () False = False
来实现fold f' [()] = False
。但是,在现在评估fold f' [(),()]
时,我们只会再次拨打step f' () False
,我们必须将其定义为False
,导致fold f' [(),()] ≡ False
,而f[(),()] ≡ True
。因此,没有unFold f
满足fold $ unFold f ≡ f
。 □
答案 1 :(得分:7)
你可以,但你需要对Fold
进行一些修改才能将其删除。
列表中的所有功能都可以表示为折叠,但有时为了实现这一点,需要额外的簿记。假设我们在Fold
类型中添加了一个额外的类型参数,该参数会传递这些额外的上下文信息。
data Fold a c r = Fold { _start :: (c, r), _step :: a -> (c,r) -> (c,r) }
现在我们可以实现fold
,就像这样
fold :: Fold a c r -> [a] -> r
fold (Fold step start) = snd . foldr step start
现在当我们试图走另一条路时会发生什么?
unFold :: ([a] -> r) -> Fold a c r
c
来自哪里?函数是不透明的值,因此很难知道如何检查函数并知道它依赖于哪些上下文信息。所以,让我们作弊。我们将“上下文信息”作为整个列表,因此当我们到达最左边的元素时,我们可以将函数应用于原始列表,忽略先前的累积结果。
unFold :: ([a] -> r) -> Fold a [a] r
unFold f = Fold { _start = ([], f [])
, _step = \a (c, _r) -> let c' = a:c in (c', f c') }
现在,遗憾的是,这不一定与fold
有关,因为它要求c
必须是[a]
。让我们通过隐藏c
与存在量化来解决这个问题。
{-# LANGUAGE ExistentialQuantification #-}
data Fold a r = forall c. Fold
{ _start :: (c,r)
, _step :: a -> (c,r) -> (c,r) }
fold :: Fold a r -> [a] -> r
fold (Fold start step) = snd . foldr step start
unFold :: ([a] -> r) -> Fold a r
unFold f = Fold start step where
start = ([], f [])
step a (c, _r) = let c' = a:c in (c', f c')
现在,fold . unFold = id
应始终如此。而且,考虑到Fold
数据类型的平等概念,您也可以说unFold . fold = id
。您甚至可以提供一个类似于旧Fold
构造函数的智能构造函数:
makeFold :: r -> (a -> r -> r) -> Fold a r
makeFold start step = Fold start' step' where
start' = ((), start)
step' a ((), r) = ((), step a r)
答案 2 :(得分:6)
TL; DR:
你原来要求的是不可能的,至少不是你想要的任何版本。 (见下文。)
如果更改您的数据类型以允许我存储中间计算,我想我会没事的,但即便如此,
函数unFold
效率相当低,这似乎与你聪明的优化技巧议程背道而驰!
列表算法的任何优化都会受到使用原始未优化函数计算步长函数的问题的影响,并且可能很复杂。
由于功能上没有相同性,因此无法优化步骤以实现高效。我认为你需要人来做unFold
,而不是编译。
无论如何,回到最初的问题:
没有。假设我们有
isSingleton :: [a] -> Bool
isSingleton [x] = True
isSingleton _ = False
然后,如果我们有unFold :: ([x] -> y) -> Fold x y
,那么如果foldSingleton
与unFold isSingleton
相同则需要
foldSingleton = Fold {start = False , step = ???}
其中step采用列表的元素并更新结果。
现在isSingleton "a" == True
,我们需要
step False = True
因为isSingleton "ab" == False
,我们需要
step True = False
所以step = not
会这么做,但isSingleton "abc" == False
所以我们还需要
step False = False
由于函数([x] -> y)
无法由Fold x y
类型的值表示,因此unFold :: ([x] -> y) -> Fold x y
不能存在函数fold . unFold = id
,因为id
是一个完整的功能。
编辑:
事实证明你并不相信这一点,因为你只期望unFold
处理具有表示折叠的函数,所以也许你的意思是unFold.fold = id
。
没有。
即使您只是希望unFold
处理可以使用([x] -> y)
获得的函数fold :: Fold x y -> ([x] -> y)
,我认为这是不可能的。让我们假设现在已经定义了
combine :: X -> Y -> Y
initial :: Y
folded :: [X] -> Y
folded = fold $ Fold initial combine
恢复值initial
非常简单:initial = folded []
。
原始combine
的恢复不是,因为没有办法从一个为Y
提供Y
值的函数,而是将X = Y = Int
的任意值组合在一起。
例如,如果我们有 combine x y | y < 0 = -10
| otherwise = y + 1
initial = 0
并且我定义了
combine
然后由于y
每次在正y
上使用folded
时只添加一个length
,并且初始值为0,folded xs
与{{1}无法区分就其输出而言。请注意,由于unFold :: ([x] -> y) -> Fold x y
永远不会为负数,因此也无法定义能够恢复combine
函数的函数fold
。这归结为Fold x y
不是单射的事实;它将[x] -> y
类型的不同值带到类型unFold :: ([x] -> y) -> Fold x y
的相同值。
因此,我证明了两件事:如果fold.unFold /= id
,那么unFold.fold /= id
和现在Fold 0 (\_ y -> y+1)
我打赌你也不相信,因为你并不关心你是否Fold 0 combine
或unFold folded
从unFold
回来,因为它们具有相同的价值什么时候重新折叠!让我们再一次缩小球门柱。也许你希望fold
只要能通过unFold
获得功能就可以工作,并且你很高兴它不会给你不一致的答案,只要你再次折叠结果,你就会得到相同的功能。我可以用下一个问题总结一下:
即。您可以定义fold.unFold
,以便fold
是通过step
可获得的功能集上的标识吗?
我确信这是不可能的,因为在不保留有关子列表中间值的额外信息的情况下计算unFold f = Fold {start = f [], step = recoverstep f}
函数不是一个可以解决的问题。
假设我们有
recoverstep f x1 initial == f [x1]
我们需要
recoverstep f x1 y | y == initial = f [x1]
所以,如果有一个x的Eq实例(敲响警钟!),那么recoverstep必须具有与
相同的效果recoverstep f x2 (f [x1]) == f [x1,x2]
我们也需要
recoverstep f x2 y | y == (f [x1]) = f [x1,x2]
所以如果有一个x的Eq实例,那么recoverstep必须具有与
相同的效果x1
但这里存在一个大问题:变量f [x1]
在此等式的右侧是空闲的。
这意味着从逻辑上讲,除非我们已经知道,否则我们无法分辨步骤函数对x应该具有的值
知道它已经使用了什么值。我们需要在折叠中存储f [x1,x2]
,unFold
等的值
数据类型使它工作,这就是为什么我们无法定义{{1}}的关键。如果更改数据类型折叠
为了让我们能够存储关于中间列表的信息,我可以看到它会起作用,但是它是不可能的
恢复上下文。
答案 3 :(得分:1)
与Dan's answer类似,但采用略有不同的方法。我们不是将累加器与将在最后抛出的部分结果配对,而是添加一个“后处理”函数,它将从累加器类型转换为最终结果。
unFold
的“欺骗”只是后处理步骤中的所有工作:
{-# LANGUAGE ExistentialQuantification #-}
data Fold a r = forall c. Fold
{ _start :: c
, _step :: a -> c -> c
, _result :: c -> r }
fold :: Fold a r -> [a] -> r
fold (Fold start step result) = result . foldr step start
unFold :: ([a] -> r) -> Fold a r
unFold f = Fold [] (:) f
makeFold :: r -> (a -> r -> r) -> Fold a r
makeFold start step = Fold start step id