Я подробно остановлюсь на том, что было сказано в комментариях здесь.Я собираюсь позаимствовать (упрощенную версию) версию GHC zipWith
, которой должно быть достаточно для этого обсуждения.
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith f [] _ = []
zipWith f _ [] = []
zipWith f (x:xs) (y:ys) = f x y : zipWith f xs ys
Теперь вот чтовычисления в конечном итоге выглядят как в великолепной бесконечной форме.
[[1, 2], [3, 4]] <.> ([[1, 2], [3, 4]] <.> ([[1, 2], [3, 4]] ... ) ... )
Хорошо, поэтому верхний уровень - <.>
.Хорошо.Давайте подробнее рассмотрим это.
zipWith (++) [[1, 2], [3, 4]] ([[1, 2], [3, 4]] <.> ([[1, 2], [3, 4]] ... ) ... )
Все еще нет проблем.Теперь мы рассмотрим шаблоны для zipWith
.Первый шаблон соответствует, только если левая сторона пуста.Welp, это определенно не правда, так что давайте двигаться дальше.Второе совпадение только в том случае, если правая сторона пуста.Итак, давайте посмотрим, если правая сторона пуста.Правая сторона выглядит так:
[[1, 2], [3, 4]] <.> ([[1, 2], [3, 4]] <.> ([[1, 2], [3, 4]] ... ) ... )
С этого мы и начали.Итак, чтобы вычислить результат, нам нужен доступ к результату.Следовательно, переполнение стека.
Теперь мы установили, что наша проблема с zipWith
.Так что давайте поиграем с этим.Во-первых, мы знаем, что будем применять это к бесконечным спискам для нашего надуманного примера, поэтому нам не нужен этот надоедливый случай с пустым списком.Избавьтесь от этого.
-- (I'm also changing the name so we don't conflict with the Prelude version)
zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith' f (x:xs) (y:ys) = f x y : zipWith' f xs ys
(<.>) :: [[a]] -> [[a]] -> [[a]]
xs <.> ys = zipWith' (++) xs ys
Но это ничего не исправляет.Нам по-прежнему приходится оценивать нормальную форму со слабой головой (читай: фигура из списка пуста), чтобы соответствовать этому шаблону.
Если бы был способ сопоставления с шаблоном без необходимости обращаться к WHNF... введите ленивые узоры .Давайте перепишем нашу функцию следующим образом.
zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith' f ~(x:xs) ~(y:ys) = f x y : zipWith' f xs ys
Теперь наша функция определенно прекратит работу, если получить конечный список.Но это позволяет нам «делать вид» совпадения с образцом в списках, фактически не выполняя никакой работы.Это эквивалентно более многословному
zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith' f xs ys = f (head xs) (head ys) : zipWith' f (tail xs) (tail ys)
И теперь мы можем правильно протестировать вашу функцию.
*Main> let x = foldr1 (<.>) $ repeat [[1, 2], [3, 4]]
*Main> x !! 0
[1,2,1,2,1,2,1,2,1,...]
*Main> x !! 1
[3,4,3,4,3,4,3,4,3,...]
Очевидный недостаток этого заключается в том, что он определенно потерпит неудачу в конечных списках, поэтому выдля них должна быть другая функция.
*Main> [[1, 2], [3, 4]] <.> [[1, 2], [3, 4]]
[[1,2,1,2],[3,4,3,4],*** Exception: Prelude.head: empty list