Контекст
Я спросил о исправлении рекурсивно определенного списка на днях. Сейчас я пытаюсь поднять его до уровня, используя вместо этого 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
, а от 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
, будучи бесконечным, равно нулю, как remainder
, , т.е. zipWith genRow pascals (tail pascals)
. Это []
или (:)
? Ну, pascals
уже был оценен как (:)
, но (tail pascals)
еще не был найден WHNF. И мы уже пытаемся, поэтому <<loop>>
.
(Извините, что изложил это словами, но мне действительно пришлось мысленно проследить это, чтобы понять это в первый раз).
Выход?
С определениями, которые у меня есть, кажется, что все определения правильные, с точки зрения потока данных. Теперь цикл выглядит просто потому, что оценщик не может решить, является ли сгенерированная структура конечной или нет. Я не могу найти способ сделать это обещанием "все бесконечно хорошо".
Я чувствую, что мне нужно обратное ленивое сопоставление: какое-то ленивое возвращение, где я могу сказать оценщику, что WHNF этого выглядит как (:)
, но вам все равно нужно будет вызвать этот thunk позже, чтобы выяснить, что в это.
Это все еще ощущается как фиксированная точка, но мне не удалось выразить таким образом, чтобы это работало.