语境
前几天我询问了如何修补递归定义的列表。我现在正试图通过对 2D 列表(列表列表)进行操作来提升它的水平。
我将以帕斯卡三角形为例,例如这个美丽的三角形:
pascals = repeat 1 : map (scanl1 (+)) pascals
[1,1,1,1,1,1...
[1,2,3,4,5...
[1,3,6,10...
[1,4,10...
[1,5...
[1...
问题
我想这样表达:
我将使用我自己的第一行和第一列(上面的示例假设第一行是
repeat 1
,这是可以修复的,而第一列是repeat (head (head pascals))
,这将更加棘手)每个元素仍然是上面一个元素和它左边一个元素的函数。
作为一个整体,它本身就是一个函数,足以在定义中插入一个补丁函数并让它传播补丁。
所以从外面看,我想找到一个f
可以这样定义的函数pascal
:
pascal p = p (f pascal)
...所以这pascal id
与示例中的相同,并pascal (patch (1,3) to 16)
产生如下内容:
[1,1,1,1, 1,1...
[1,2,3,16,17...
[1,3,6,22...
[1,4,10...
[1,5...
[1...
我在哪里
让我们首先定义并提取第一行和第一列,这样我们就可以让它们可用,而不会试图滥用它们的内容。
element0 = 1
row0 = element0 : repeat 1
col0 = element0 : repeat 1
更新要使用的定义row0
很容易:
pascals = row0 : map (scanl1 (+)) pascals
但第一列仍然是element0
. 更新以从以下位置获取它们col0
:
pascals = row0 : zipWith newRow (tail col0) pascals
where
newRow leftMost prevRow = scanl (+) leftMost (tail prevRow)
现在我们可以满足第一个要求(自定义第一行和第一列)。没有补丁,第二个还是不错的。
我们甚至得到了第三部分的一部分:如果我们修补一个元素,它会向下传播,因为它newRow
是根据prevRow
. 但它不会向右传播,因为(+)
操作在scanl
的内部累加器和 fromleftMost
上,这在这种情况下是显式的。
我试过的
从那里开始,似乎正确的做法是真正分离关注点。我们希望我们的初始化程序在定义row0
中col0
尽可能明确,并找到一种方法来独立定义矩阵的其余部分。存根:
pascals = row0 : zipWith (:) (tail col0) remainder
[1,1,1,1,1,1,1,1,1,1...
[1,/-------------------
[1,|
[1,|
[1,|
[1,| remainder
[1,|
[1,|
[1,|
[1,|
然后我们希望直接根据整体定义余数。自然的定义是:
remainder = zipWith genRow pascals (tail pascals)
where genRow prev cur = zipWith (+) (tail prev) cur
[1,1,1,1,1,1,1,1,1,1...
<<loop>>
第一行出来很好。为什么是循环?评估后有帮助: pascals
被定义为一个缺点,他的车很好(并且打印)。什么是cdr?是zipWith (:) (tail col0) remainder
。这个表达式是 a[]
还是(:)
?它是其论点中最短的,tail col0
并且remainder
. 是无限的,它与, iecol0
一样为空。那是还是?好吧,已经评估为,但尚未找到 WHNF。我们已经在尝试,所以。remainder
zipWith genRow pascals (tail pascals)
[]
(:)
pascals
(:)
(tail pascals)
<<loop>>
(很抱歉用单词拼写出来,但我真的不得不像这样在脑海中追踪它才能第一次理解它)。
出路?
根据我的定义,似乎所有定义都是正确的,数据流明智的。循环现在看起来很简单,因为评估器无法确定生成的结构是否是有限的。我找不到办法让它成为一个承诺“它是无限的”。
我觉得我需要一些惰性匹配的逆向:一些惰性返回,我可以告诉评估者这个 WHNF 出来(:)
,但你仍然需要稍后调用这个 thunk 来找出其中的内容。
它仍然感觉像是一个固定点,但我还没有设法以一种有效的方式表达。