Есть несколько предостережений. Прежде всего, ваша recommendPage
функция небезопасна, поскольку она не работает в пустых списках. Лучше избегать таких функций, как head
и tail
, которые не являются полными и не работают на некоторых входах. Во-вторых, логически кажется более естественным вместо этого иметь recommendNPages
функцию в качестве «основной», а затем реализовать recommendPage
как recommendNPages 1
.
Но я собираюсь предположить, что вы есть веские причины делать то, что вы пытаетесь делать именно таким образом. Тогда вы могли бы использовать эту простую реализацию:
recommendNPages :: Int -> Blog -> ([Post], Blog)
recommendNPages 0 blog = ([], blog)
recommendNPages n blog =
case recommendPage blog of
(post, blog') -> case recommendNPages (n - 1) blog' of
(posts, blog'') -> (post : posts, blog'')
Однако всякий раз, когда вы видите этот шаблон, вы должны подозревать, что задействовано состояние, что вы и сделали, судя по слову «stateful» в заголовке question:).
В самом деле, вы можете рассматривать вашу функцию recommendPage
как вычисление с учетом состояния, которое «изменяет» блог, извлекая из него одну страницу, и тогда ваш recommendNPages
становится просто повторением этого вычисления с учетом состояния n
раз.
Инструмент, который мы используем в Haskell для такого рода вычислений, называется State
монадой . В сочетании с функцией replicateM
, которая повторяет вычисление монади c n
раз, мы получаем:
recommendNPages :: Int -> Blog -> ([Post], Blog)
recommendNPages n = runState $ replicateM n step
where
-- | One step of our computation. (Just wrapping it into `State`.)
step :: State Blog Post
step = state recommendPage