Вы можете представить список как функцию для построения списка из []
list1, list2 :: [Integer] -> [Integer]
list1 = \xs -> 1 : 2 : 3 : xs
list2 = \xs -> 4 : 5 : 6 : xs
Затем вы можете легко добавлять списки и добавлять их в любой конец.
list1 . list2 $ [] -> [1,2,3,4,5,6]
list2 . list1 $ [] -> [4,5,6,1,2,3]
(7:) . list1 . (8:) . list2 $ [9] -> [7,1,2,3,8,4,5,6,9]
Вы можете переписать zipWith, чтобы вернуть эти частичные списки:
zipWith' _ [] _ = id
zipWith' _ _ [] = id
zipWith' f (x:xs) (y:ys) = (f x y :) . zipWith' f xs ys
А теперь вы можете написать ptri как:
ptri = [] : mkptri ptri
mkptri (xs:yss) = newRow : mkptri yss
where newRow = zipWith' (+) xs (0:xs) [1]
Если продолжить, приведем однострочник, который более симметричен:
ptri = ([] : ) . map ($ []) . iterate (\x -> zipWith' (+) (x [0]) (0 : x [])) $ (1:)
Или это еще проще:
ptri = [] : iterate (\x -> 1 : zipWith' (+) (tail x) x [1]) [1]
Или без zipWith '(mapAccumR находится в Data.List):
ptri = [] : iterate (uncurry (:) . mapAccumR (\x x' -> (x', x+x')) 0) [1]