Придуманный пример, но приведенный ниже код демонстрирует класс проблем, с которыми я сталкиваюсь, изучая Haskell.
import Control.Monad.Error
import Data.Char (isDigit)
countDigitsForList [] = return []
countDigitsForList (x:xs) = do
q <- countDigits x
qs <- countDigitsForList xs
return (q:qs)
countDigits x = do
if all isDigit x
then return $ length x
else throwError $ "Bad number: " ++ x
t1 = countDigitsForList ["1", "23", "456", "7890"] :: Either String [Int]
t2 = countDigitsForList ["1", "23", "4S6", "7890"] :: Either String [Int]
t1
дает мне правильный ответ, а t2
правильно определяет ошибку.
Мне кажется, что для достаточно длинного списка этот код будет исчерпывать пространство стека, потому что он выполняется внутри монады, и на каждом шаге он пытается обработать остальную часть списка перед возвратомрезультат.
Аккумулятор и хвостовая рекурсия могут решить проблему, но я неоднократно читал, что в Haskell нет необходимости из-за ленивых вычислений.
Как структурировать код такого типа втот, который не будет иметь проблемы с пространством стека и / или будет ленивым?