这是我先前的question的后续行动。
我试图从here了解Haskell中的列表拆分示例:
foldr (\a ~(x,y) -> (a:y,x)) ([],[])
我可以阅读Haskell并知道foldr
是什么,但不理解此代码。您能否带我看一下这段代码,并对其进行更详细的说明?
让我们尝试在示例输入列表上运行此功能,例如[1,2,3,4,5]
:
foldr (\a ~(x,y) -> (a:y,x)) ([],[]) [1,5]
开始。这里的a
是列表的第一个元素,而(x,y)
以([],[])
开始,因此(a:y,x)
返回([1],[])
。a = 2
和(x,y) = ([1],[])
,所以(a:y,x) = ([2],[1])
。请注意,列表的顺序已交换。每次迭代将再次交换列表。但是,输入列表的下一个元素将始终添加到第一个列表中,这就是拆分的方式。a = 3
和(x,y) = ([2],[1])
,所以(a:y,x) = ([3,1],[2])
。a = 4
和(x,y) = ([3,[2])
,所以(a:y,x) = ([4,2],[3,1])
。a = 4
和(x,y) = ([4,1])
,所以(a:y,x) = ([5,[4,2])
。([5,2])
。如演练所示,split函数的工作方式是维护两个列表,在每次迭代时交换它们,并将输入的每个元素附加到另一个列表中。
,我们可以看一个例子。例如,如果我们有一个列表[1,5]
。如果我们以此方式处理列表,那么我们看到foldr
的计算方式为:
foldr (\a ~(x,5]
因此,a
首先是列表的第一项,然后它将返回类似以下内容的内容:
(1:y,x)
where (x,y) = foldr (\a ~(x,[]) [4,5]
请注意,当我们将(x,y)
放在2元组的第一项之前,a
元组已被交换 。
(1:y,x)
where (x,y) = (4:y',x')
(x',y') = foldr (\a ~(x,[]) [2,5]
如果继续这样做,我们将获得:
(1:y,y) = (4:y',x')
(x',y') = (2:y'',x'')
(x'',y'') = (5:y''',x''')
(x''',y''') = foldr (\a ~(x,[]) []
自从我们到达列表的末尾以来,我们因此获得了foldr … ([],[]) []
的2元组([],[])
:
(1:y,y') = (2:y'',x'')
(x'',y'') = (5:y''',x''')
(x''',y''') = ([],[])
所以x''' = []
和y''' = []
,因此可以解决:
(1:y,y'') = (5:[],[])
(x''',[])
所以x'' = [5]
和y'' = []
:
(1:y,y') = (2:[],[5])
(x'',[])
所以x' = [5]
和y' = [2]
:
(1:y,y) = (4:[5],[2])
(x',[])
所以x = [4,5]
和y = [2]
如此,我们最终获得:
(1:[2],5])
where (x,[])
所以结果就是预期的([1,5])
。
大约
foldr (\a ~(x,[]) [a,b,c,d,e]
=
let g a ~(x,y) = (a:y,x) in
g a $ g b $ g c $ g d $ g e ([],[])
=
g a $ g b $ g c $ g d $ ([e],[])
=
g a $ g b $ g c $ ([d],[e])
=
g a $ g b $ ([c,e],[d])
=
g a $ ([b,d],[c,e])
=
([a,[b,d])
但实际上,
foldr (\a ~(x,x) in
g a $ foldr g ([],[]) [b,e]
=
(a:y,x) where
(x,y) = foldr g ([],y) = (b:y2,x2) where
(x2,y2) = foldr g ([],[]) [c,y2) = (c:y3,x3) where
(x3,y3) = (d:y4,x4) where
(x4,y4) = (e:y5,x5) where
(x5,y5) = ([],[])
(通过访问(如果和何时)以自上而下的方式被强制使用,例如,逐渐充实
=
(a:x2,b:y2) where
(x2,[])
=
(a:c:y3,b:x3) where
(x3,[])
=
(a:c:x4,b:d:y4) where
(x4,[])
=
(a:c:e:y5,b:d:x5) where
(x5,[])
=
(a:c:e:[],b:d:[])
但是强制可能会以不同的顺序执行,具体取决于调用方式,例如
print . (!!1) . snd $ foldr (\a ~(x,e]
print . (!!2) . fst $ foldr (\a ~(x,e]
等
编辑:为解决有关惰性模式的问题,这样做是为了使所得函数适当地惰性:
foldr
,其第二个参数中的组合功能为 strict ,对 recursion 进行编码,即自下而上。首先构造递归处理列表的其余部分的结果,然后将其头部与之合并。
foldr
,在第二个参数中具有 lazy 的合并功能,对 corecursion 进行编码,即自上而下。首先构造结果值的头部,然后其余部分填充。在Prolog和其他地方,这很让人联想到 tail recursion modulo cons 。惰性评估作为一个概念来自“ CONS
,不应评估其参数”; TRMC直到稍后才对构造函数评估 second 参数,这才是真正重要的。
让我们将折页翻译掉。
splatter :: [a] -> ([a],[a])
splatter = foldr (\a ~(x,[])
这是什么意思?列表的foldr
已定义
foldr :: (a -> r -> r) -> r -> [a] -> r
foldr k z = go
where
go [] = z
go (p : ps) = p `k` go ps
内联并简化:
splatter = go
where
go [] = ([],[])
go (p : ps) =
(\a ~(x,x)) p (go ps)
splatter = go
where
go [] = ([],[])
go (p : ps) =
(\ ~(x,y) -> (p:y,x)) (go ps)
splatter = go
where
go [] = ([],[])
go (p : ps) =
let (x,y) = go ps
in (p : y,x)
let
中的默认延迟模式匹配意味着在有人强迫x
或y
之前,我们实际上并没有进行递归调用。
要注意的关键是在每个递归调用上x
和y
交换位置。这导致了交替模式。
因此,一切都发生在\a ~(x,x)
函数中,其中a
首先是所提供列表中的最后一项,而(x,y)
是一个以{{1}开头的交替元组累加器}。 ([],[])
将当前元素放在y
的前面,但是元组中的a:y
和x
列表将被交换。
但是,值得一提的是,所有新的附加项都在元组的第一侧返回,这保证了第一侧最终从列表的第一项开始,因为它被附加了最后一项。
因此,对于y
的列表,请按以下步骤操作
[1,5,6]
关于波浪号a (x,y) return
----------------------------------
6 ([],[] ) (6:y,x)
5 ([6],[] ) (5:y,x)
4 ([5],[6] ) (4:y,x)
3 ([4,6],[5] ) (3:y,x)
2 ([3,5],6] ) (2:y,x)
1 ([2,5] ) (1:y,x)
[] ([1,[2,6]) no return
的操作,最好在Haskell指南的Haskell/Laziness主题中进行如下描述
在图案前加上波浪号会延迟对 直到组件实际使用为止。但是你运行 该值可能与模式不匹配的风险-您是在告诉 编译器“相信我,我知道它会解决的”。 (如果事实证明, 与模式不匹配,则会出现运行时错误。) 区别:
~
,在第一个示例中,计算值是因为它必须匹配 元组模式。您评估未定义并得到未定义,这 停止诉讼。在后一个示例中,您无需打扰 评估参数直到需要它为止,结果是 永远不会,所以没有定义传递它也没关系。
有效地,折叠功能交替出现,从而将输入列表中的下一项添加到其中。在类似Python这样的语言中,类似的功能应该是
def split(xs):
a0 = a = []
b0 = b = []
for x in xs:
a.append(x)
a,b = b,a
return a0,b0
使用惰性模式有两个原因:
foldr
使用所有输入考虑以下示例:
let (odds,evens) = foldr (\a ~(x,[]) $ [1..]
in take 5 odds
结果为[1,7,9]
。
如果您放弃了惰性模式并使用了
let (odds,evens) = foldr (\a (x,[]) $ [1..]
in take 10 odds
代码将永远不会终止,因为take
如果不先计算整个奇数列表就无法获得第一个元素(更不用说前五个)。
那是为什么?考虑一下Data.List.foldr
的定义:
foldr k z = go
where
go [] = z
go (y:ys) = y `k` go ys
如果两个参数中的k = \a (x,x)
都严格,则在达到y `k` go ys
的基本情况之前,对go
的求值不会终止。
使用惰性模式,该功能等效于
\a p -> (a:snd p,fst p)
意味着我们永远不必在p
或fst
上匹配snd
;该函数现在在其第二个参数中是惰性的。这意味着
go (y:ys) = y `k` go ys
= (\a p -> (a:snd p,fst p)) y (go ys)
= let p = go ys in (y:snd p,fst p)
立即返回,无需进一步评估go
。只有当我们尝试获取任一列表的第二个元素时,我们才需要再次调用go
,但是再次,我们只需要前进一个步骤即可。