语境
我问
patching a recursively-defined list
前几天。我现在正试图通过操作一个二维列表(列表列表)来提高它的级别。
我将使用帕斯卡三角形作为示例,例如
this beautiful one
:
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
的内部收集器,以及
leftMost
,在此上下文中是显式的。
我试过什么
从那里看来,正确的做法似乎是真正分离关注点。我们需要初始值设定项
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
. 那个表达方式是
[]
或
(:)
?这是最短的论点
tail col0
和
remainder
.
COL0
因为是无限的,它就像
余数
,
即
zipWith genRow pascals (tail pascals)
. 那是
[]
或
(:)
?好,
帕斯卡
已评估为
(:)
但是
(tail pascals)
还没有找到WHNF。我们已经在尝试了,所以
<<loop>>
.
(很抱歉用词拼写出来,但我真的必须在精神上像这样追溯才能第一次理解它)。
出路?
根据我的定义,似乎所有的定义都是正确的,数据流方面的。现在的循环似乎只是因为评估器无法决定生成的结构是否是有限的。我找不到一种方法让它成为一个承诺“它是无限的,好吧”。
我觉得我需要一些关于懒惰匹配的对话:一些懒惰的返回,在那里我可以告诉评估者这其中的原因是
(:)
但是你还是需要稍后再打电话给thunk来找出里面有什么。
它仍然感觉像一个固定的点,但我还没有设法用一种有效的方式表达。