我正在阅读Some Tricks for List Manipulation,其中包含以下内容:
zipRev xs ys = foldr f id xs snd (ys,[]) where f x k c = k (\((y:ys),r) -> c (ys,(x,y):r))
我们在这里看到的是,我们在顶部堆叠了两个延续 彼此的。发生这种情况时,他们通常可以“取消”,例如 所以:
zipRev xs ys = snd (foldr f (ys,[]) xs) where f x (y:ys,r) = (ys,(x,y):r)
我不明白您如何“取消”堆叠的延续,以从顶部的代码块到达底部的代码块。您希望通过哪种模式进行转换,为什么会起作用?
答案 0 :(得分:16)
函数f :: a -> b
可以在双重延续中被伪装成函数f' :: ((a -> r1) -> r2) -> ((b -> r1) -> r2)
。
obfuscate :: (a -> b) -> ((a -> r1) -> r2) -> (b -> r1) -> r2
obfuscate f k2 k1 = k2 (k1 . f)
obfuscate
具有很好的属性,可以保留函数的组成和身份:您可以通过几步证明obfuscate f . obfuscate g === obfuscate (f . g)
和obfuscate id === id
。这意味着您可以经常使用此转换来解开构成obfuscate
d函数的双连续计算,方法是将obfuscate
从合成中分解出来。这个问题就是这种纠结的一个例子。
顶部代码块中的f
是底部块obfuscate
的{{1}} d版本(更确切地说,顶部f
是{{1} } d版本的底部f x
)。您可以注意到顶部obfuscate
如何将外部延续应用到转换其输入的函数,然后将整个事物应用于内部延续,就像在f x
主体中一样。
所以我们可以开始解开f
:
obfuscate
由于zipRev
的作用是彼此组成一堆zipRev xs ys = foldr f id xs snd (ys,[])
where
f x = obfuscate (\(y:ys,r) -> (ys,(x,y):r))
d函数(并将其全部应用到foldr
,我们可以在右边保留),我们可以将obfuscate
分解到整个折叠的外部:
id
现在应用obfuscate
的定义并简化:
zipRev xs ys = obfuscate (\accum -> foldr f accum xs) id snd (ys,[])
where
f x (y:ys,r) = (ys,(x,y):r)
QED!
答案 1 :(得分:12)
提供功能
g :: a₁ -> a₂
我们可以将其提升为连续功能,切换顺序:
lift g = (\c a₁ -> c (g a₁))
:: (a₂ -> t) -> a₁ -> t
此变换是一个逆函数,也就是说,它通过切换顺序与函数组成交互:
g₁ :: a₁ -> a₂
g₂ :: a₂ -> a₃
lift g₁ . lift g₂
== (\c₁ a₁ -> c₁ (g₁ a₁)) . (\c₂ a₂ -> c₂ (g₂ a₂))
== \c₂ a₁ -> (\a₂ -> c₂ (g₂ a₂)) (g₁ a₁)
== \c₂ a₁ -> c₂ (g₂ (g₁ a₁))
== lift (g₂ . g₁)
:: (a₃ -> t) -> a₁ -> t
lift id
== (\c a₁ -> c a₁)
== id
:: (a₁ -> t) -> a₁ -> t
我们可以以与堆叠连续体上的函数相同的方式再次提升被提升的函数,只需将顺序切换回去:
lift (lift g)
== (\k c -> k ((\c a₁ -> c (g a₁)) c))
== (\k c -> k (\a₁ -> c (g a₁)))
:: ((a₁ -> t) -> u) -> (a₂ -> t) -> u
堆叠两个逆变函子可以得到一个(协变)函子:
lift (lift g₁) . lift (lift g₂)
== lift (lift g₂ . lift g₁)
== lift (lift (g₁ . g₂))
:: ((a₁ -> t) -> u) -> (a₃ -> t) -> u
lift (lift id)
== lift id
== id
:: ((a₁ -> t) -> u) -> (a₁ -> t) -> u
这就是您的示例中使用g = \(y:ys, r) -> (ys, (x, y):r)
进行的逆向转换。 g
是内同态(a₁ = a₂
),而foldr
正在将其一堆副本与各种x
组合在一起。我们正在做的是用函数组成的双重提升来代替双重提升函数的合成,这只是对函子定律的归纳应用:
f :: x -> a₁ -> a₁
c :: (a₁ -> t) -> u
xs :: [x]
foldr (\x -> lift (lift (f x))) c xs
== lift (lift (\a₁ -> foldr f a₁ xs)) c
:: (a₁ -> t) -> u
答案 2 :(得分:2)
让我们尝试从基本的角度理解此代码。一个人奇怪,它甚至做什么?
zipRev xs ys = foldr f id xs snd (ys,[])
where
-- f x k c = k (\(y:ys, r) -> c (ys, (x,y):r))
f x k c = k (g x c)
-- = (k . g x) c -- so,
-- f x k = k . g x
g x c (y:ys, r) = c (ys, (x,y):r)
在这里,我们使用lambda lifting恢复了g
combinator。
因此,由于f x k = k . g x
是k
到x
的左侧,因此输入列表被转换为反向组成链,
foldr f id [x1, x2, x3, ..., xn] where f x k = k . g x
===>>
(((...(id . g xn) . ... . g x3) . g x2) . g x1)
因此,它只是完成了左折操作
zipRev [] ys = []
zipRev [x1, x2, x3, ..., xn] ys
= (id . g xn . ... . g x3 . g x2 . g x1) snd (ys, [])
= g xn (g xn1 ( ... ( g x3 ( g x2 ( g x1 snd)))...)) (ys, [])
where ----c--------------------------------------------
g x c (y:ys, r) = c (ys, (x,y):r)
因此,我们进入了xs
列表的最底端,然后在从右到右的方式返回从左到右(即从上到下)使用ys
列表-在xs
列表上为左(即自下而上)。直接将其编码为带有严格的reducer的右折,因此流的确在xs
上从右到左。链中最底端的动作(snd
)最后执行,因此在新代码中,它成为最顶端的操作(仍最后执行):
zipRev xs ys = snd (foldr h (ys,[]) xs)
where
h x (y:ys, r) = (ys, (x,y):r)
g x c
被用作原始代码的延续,c
被用作第二层延续;但实际上一直以来都是从右边开始的常规折叠。
因此,确实可以将反向的第一个列表与第二个列表压缩在一起。这也不安全;它错过了一个子句:
g x c ([], r) = c ([], r) -- or just `r`
g x c (y:ys, r) = c (ys, (x,y):r)
(更新:) duplode(和Joseph Sible)的答案使lambda的举升方式有所不同,从而更适合该任务。它是这样的:
zipRev xs ys = foldr f id xs snd (ys,[])
where
f x k c = k (\((y:ys), r) -> c (ys, (x,y):r))
= k (c . (\((y:ys), r) -> (ys, (x,y):r)) )
= k (c . g x)
g x = (\((y:ys), r) -> (ys, (x,y):r))
{- f x k c = k ((. g x) c) = (k . (. g x)) c = (. (. g x)) k c
f x = (. (. g x)) -}
那么
foldr f id [ x1, x2, ... , xn ] snd (ys,[]) =
= ( (. (. g x1)) $ (. (. g x2)) $ ... $ (. (. g xn)) id ) snd (ys,[]) -- 1,2...n
= ( id . (. g xn) . ... . (. g x2) . (. g x1) ) snd (ys,[]) -- n...2,1
= ( snd . g x1 . g x2 . ... . g xn ) (ys,[]) -- 1,2...n!
= snd $ g x1 $ g x2 $ ... $ g xn (ys,[])
= snd $ foldr g (ys,[]) [x1, x2, ..., xn ]
简单。 :)翻转两次根本不翻转。
答案 3 :(得分:2)
让我们先进行一些外观调整:
-- Note that `g x` is an endomorphism.
g :: a -> ([b], [(a,b)]) -> ([b], [(a,b)])
g x ((y:ys),r) = (ys,(x,y):r)
zipRev xs ys = foldr f id xs snd (ys,[])
where
f x k = \c -> k (c . g x)
f
将延续(c . g x
)传递给另一个函数(k
,“双重延续”,as user11228628 puts it)。
尽管我们可以合理地预期,f
的重复使用会随着折叠的进行而以某种方式构成由列表元素构成的g x
内态,但是内态的构成顺序可能不会立即变得显而易见,因此我们最好走几步,以确保:
-- x0 is the first element, x1 the second, etc.
f x0 k0
\c -> k0 (c . g x0)
\c -> (f x1 k1) (c . g x0) -- k0 is the result of a fold step.
\c -> (\d -> k1 (d . g x1)) (c . g x0) -- Renaming a variable for clarity.
\c -> k1 (c . g x0 . g x1)
-- etc .
-- xa is the *last* element, xb the next-to-last, etc.
-- ka is the initial value passed to foldr.
\c -> (f xa ka) (c . g x0 . g x1 . . . g xb)
\c -> (\d -> ka (d . g xa)) (c . g x0 . g x1 . . . g xb)
\c -> ka (c . g x0 . g x1 . . . g xb . g xa)
ka
是传递给foldr的初始值,是id
,这使事情变得更加简单:
foldr f id xs = \c -> c . g x0 . g x1 . . . g xa
由于我们对传递给c
的{{1}}自变量所做的所有工作都是使用内同态对它进行后期组合,因此我们不妨一味地将其分解:
foldr f id xs
请注意我们是如何从zipRev xs ys = (snd . foldr h id xs) (ys,[])
where
h x e = g x . e
转到c . g x
的。可以将其描述为原始实施中CPS欺骗的附带影响。
最后一步是注意g x . e
与我们对implement foldr
in terms of foldMap
for the Endo
monoid所做的精确对应。或者,更明确地说:
h x e = g x . e
这最终使我们找到了想要的东西:
foldEndo g i xs = foldr g i xs -- The goal is obtaining an Endo-like definition.
foldEndo _ i [] = i
foldEndo g i (x : xs) = g x (foldEndo g i xs)
foldEndo g i xs = go xs i
where
go [] = \j -> j
go (x : xs) = \j -> g x (foldEndo g j xs)
foldEndo g i xs = go xs i
where
go [] = \j -> j
go (x : xs) = \j -> g x (go xs j)
foldEndo g i xs = go xs i
where
go [] = id
go (x : xs) = g x . go xs
foldEndo g i xs = go xs i
where
h x e = g x . e
go [] = id
go (x : xs) = h x (go xs)
foldEndo g i xs = go xs i
where
h x e = g x . e
go xs = foldr h id xs
foldEndo g i xs = foldr h id xs i
where
h x e = g x . e
答案 4 :(得分:2)
user11228628's answer使我理解。这是我阅读本书时的一些见解,以及一些逐步的转换。
\k c -> k (c . f)
的{{1}}(或者,如果您喜欢不可读的无指向的(. (. f))
)(请注意,f
不是lambda的参数。f
是它们对obfuscate
的定义。fmap
中提取类似功能的技巧适用于可能是有效的foldr
的任何功能。fmap
将zipRev xs ys = foldr f id xs snd (ys,[])
where
f x k c = k (\((y:ys),r) -> c (ys,(x,y):r))
拉出λ
c
用zipRev xs ys = foldr f id xs snd (ys,[])
where
f x k c = k (c . \((y:ys),r) -> (ys,(x,y):r))
代替其定义
obfuscate
将zipRev xs ys = foldr f id xs snd (ys,[])
where
f x = obfuscate (\((y:ys),r) -> (ys,(x,y):r))
拉出λ
obfuscate
从zipRev xs ys = foldr f id xs snd (ys,[])
where
f = obfuscate . \x ((y:ys),r) -> (ys,(x,y):r)
中抽出obfuscate
f
由于zipRev xs ys = foldr (obfuscate . f) id xs snd (ys,[])
where
f x ((y:ys),r) = (ys,(x,y):r)
遵循函子定律,我们可以将其从obfuscate
中撤出
foldr
内联zipRev xs ys = obfuscate (flip (foldr f) xs) id snd (ys,[])
where
f x ((y:ys),r) = (ys,(x,y):r)
obfuscate
Beta减少
zipRev xs ys = (\k c -> k (c . flip (foldr f) xs)) id snd (ys,[])
where
f x ((y:ys),r) = (ys,(x,y):r)
简化
zipRev xs ys = (id (snd . flip (foldr f) xs)) (ys,[])
where
f x ((y:ys),r) = (ys,(x,y):r)
zipRev xs ys = snd (foldr f (ys,[]) xs)
where
f x (y:ys,r) = (ys,(x,y):r)
中提取有效fmap
的函数的理由foldr
展开foldr (fmap . f) z [x1,x2,...,xn]
foldr
内联(fmap . f) x1 . (fmap . f) x2 . ... . (fmap . f) xn $ z
内联
.
应用仿函数定律
fmap (f x1) . fmap (f x2) . ... . fmap (f xn) $ z
使用扩展括号中的部分
fmap (f x1 . f x2 . ... . f xn) $ z
根据fmap (\z2 -> f x1 . f x2 . ... . f xn $ z2) z
foldr
根据fmap (\z2 -> foldr f z2 [x1,x2,...,xn]) z
flip
fmap (flip (foldr f) [x1,x2,...,xn]) z
中提取有效contramap
的函数的理由foldr
展开foldr (contramap . f) z [x1,x2,...,xn]
foldr
内联(contramap . f) x1 . (contramap . f) x2 . ... . (contramap . f) xn $ z
内联
.
应用逆定律
contramap (f x1) . contramap (f x2) . ... . contramap (f xn) $ z
使用扩展括号中的部分
contramap (f xn . ... . f x2 . f x1) $ z
根据contramap (\z2 -> f xn . ... . f x2 . f x1 $ z2) z
foldr
根据contramap (\z2 -> foldr f z2 [xn,...,x2,x1]) z
flip
应用contramap (flip (foldr f) [xn,...,x2,x1]) z
= foldr f z (reverse xs)
foldl (flip f) z xs